1 /* 2 * Copyright (C) 2019 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.p2p; 18 19 import android.net.wifi.p2p.WifiP2pConfig; 20 import android.net.wifi.p2p.WifiP2pGroup; 21 import android.net.wifi.p2p.WifiP2pGroupList; 22 import android.util.Log; 23 24 import com.android.server.wifi.Clock; 25 import com.android.server.wifi.proto.WifiStatsLog; 26 import com.android.server.wifi.proto.nano.WifiMetricsProto.GroupEvent; 27 import com.android.server.wifi.proto.nano.WifiMetricsProto.P2pConnectionEvent; 28 import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiP2pStats; 29 30 import java.io.PrintWriter; 31 import java.util.ArrayList; 32 import java.util.Calendar; 33 import java.util.Collection; 34 import java.util.List; 35 36 /** 37 * Provides storage for wireless connectivity P2p metrics, as they are generated. 38 * Metrics logged by this class include: 39 * Aggregated connection stats (num of connections, num of failures, ...) 40 * Discrete connection event stats (time, duration, failure codes, ...) 41 */ 42 public class WifiP2pMetrics { 43 private static final String TAG = "WifiP2pMetrics"; 44 private static final boolean DBG = false; 45 46 private static final int MAX_CONNECTION_EVENTS = 256; 47 private static final int MAX_GROUP_EVENTS = 256; 48 49 private Clock mClock; 50 private final Object mLock = new Object(); 51 52 /** 53 * Metrics are stored within an instance of the WifiP2pStats proto during runtime, 54 * The P2pConnectionEvent and GroupEvent metrics are stored during runtime in member 55 * lists of this WifiP2pMetrics class, with the final WifiLog proto being pieced 56 * together at dump-time 57 */ 58 private final WifiP2pStats mWifiP2pStatsProto = 59 new WifiP2pStats(); 60 61 /** 62 * Connection information that gets logged for every P2P connection attempt. 63 */ 64 private final List<P2pConnectionEvent> mConnectionEventList = 65 new ArrayList<>(); 66 67 /** 68 * The latest started (but un-ended) connection attempt 69 */ 70 private P2pConnectionEvent mCurrentConnectionEvent; 71 72 /** 73 * The latest started (but un-ended) connection attempt start time 74 */ 75 private long mCurrentConnectionEventStartTime; 76 77 /** 78 * Group Session information that gets logged for every formed group. 79 */ 80 private final List<GroupEvent> mGroupEventList = 81 new ArrayList<>(); 82 83 /** 84 * The latest started (but un-ended) group 85 */ 86 private GroupEvent mCurrentGroupEvent; 87 88 /** 89 * The latest started (but un-ended) group start time 90 */ 91 private long mCurrentGroupEventStartTime; 92 93 /** 94 * The latest started (but un-ended) group idle start time. 95 * The group is idle if there is no connected client. 96 */ 97 private long mCurrentGroupEventIdleStartTime; 98 99 /** 100 * The current number of persistent groups. 101 * This should be persisted after a dump. 102 */ 103 private int mNumPersistentGroup; 104 WifiP2pMetrics(Clock clock)105 public WifiP2pMetrics(Clock clock) { 106 mClock = clock; 107 108 mNumPersistentGroup = 0; 109 } 110 111 /** 112 * Clear all WifiP2pMetrics, except for currentConnectionEvent. 113 */ clear()114 public void clear() { 115 synchronized (mLock) { 116 mConnectionEventList.clear(); 117 if (mCurrentConnectionEvent != null) { 118 mConnectionEventList.add(mCurrentConnectionEvent); 119 } 120 mGroupEventList.clear(); 121 if (mCurrentGroupEvent != null) { 122 mGroupEventList.add(mCurrentGroupEvent); 123 } 124 mWifiP2pStatsProto.clear(); 125 } 126 } 127 128 /** 129 * Put all metrics that were being tracked separately into mWifiP2pStatsProto 130 */ consolidateProto()131 public WifiP2pStats consolidateProto() { 132 synchronized (mLock) { 133 mWifiP2pStatsProto.numPersistentGroup = mNumPersistentGroup; 134 int connectionEventCount = mConnectionEventList.size(); 135 if (mCurrentConnectionEvent != null) { 136 connectionEventCount--; 137 } 138 mWifiP2pStatsProto.connectionEvent = 139 new P2pConnectionEvent[connectionEventCount]; 140 for (int i = 0; i < connectionEventCount; i++) { 141 mWifiP2pStatsProto.connectionEvent[i] = mConnectionEventList.get(i); 142 } 143 144 int groupEventCount = mGroupEventList.size(); 145 if (mCurrentGroupEvent != null) { 146 groupEventCount--; 147 } 148 mWifiP2pStatsProto.groupEvent = 149 new GroupEvent[groupEventCount]; 150 for (int i = 0; i < groupEventCount; i++) { 151 mWifiP2pStatsProto.groupEvent[i] = mGroupEventList.get(i); 152 } 153 return mWifiP2pStatsProto; 154 } 155 } 156 157 /** 158 * Dump all WifiP2pMetrics. Collects some metrics at this time. 159 * 160 * @param pw PrintWriter for writing dump to 161 */ dump(PrintWriter pw)162 public void dump(PrintWriter pw) { 163 synchronized (mLock) { 164 pw.println("WifiP2pMetrics:"); 165 pw.println("mConnectionEvents:"); 166 for (P2pConnectionEvent event : mConnectionEventList) { 167 StringBuilder sb = new StringBuilder(); 168 Calendar c = Calendar.getInstance(); 169 c.setTimeInMillis(event.startTimeMillis); 170 sb.append("startTime="); 171 if (event.startTimeMillis == 0) { 172 sb.append(" <null>"); 173 } else { 174 sb.append(c.get(Calendar.MONTH)).append("-") 175 .append(c.get(Calendar.DAY_OF_MONTH)).append(" ") 176 .append(c.get(Calendar.HOUR_OF_DAY)).append(":") 177 .append(c.get(Calendar.MINUTE)).append(":") 178 .append(c.get(Calendar.SECOND)).append(".") 179 .append(c.get(Calendar.MILLISECOND)); 180 } 181 sb.append(", connectionType="); 182 switch (event.connectionType) { 183 case P2pConnectionEvent.CONNECTION_FRESH: 184 sb.append("FRESH"); 185 break; 186 case P2pConnectionEvent.CONNECTION_REINVOKE: 187 sb.append("REINVOKE"); 188 break; 189 case P2pConnectionEvent.CONNECTION_LOCAL: 190 sb.append("LOCAL"); 191 break; 192 case P2pConnectionEvent.CONNECTION_FAST: 193 sb.append("FAST"); 194 break; 195 default: 196 sb.append("UNKNOWN"); 197 break; 198 } 199 sb.append(", wpsMethod="); 200 switch (event.wpsMethod) { 201 case P2pConnectionEvent.WPS_NA: 202 sb.append("NA"); 203 break; 204 case P2pConnectionEvent.WPS_PBC: 205 sb.append("PBC"); 206 break; 207 case P2pConnectionEvent.WPS_DISPLAY: 208 sb.append("DISPLAY"); 209 break; 210 case P2pConnectionEvent.WPS_KEYPAD: 211 sb.append("KEYPAD"); 212 break; 213 case P2pConnectionEvent.WPS_LABEL: 214 sb.append("LABLE"); 215 break; 216 default: 217 sb.append("UNKNOWN"); 218 break; 219 } 220 sb.append(", durationTakenToConnectMillis="); 221 sb.append(event.durationTakenToConnectMillis); 222 sb.append(", groupRole="); 223 switch (event.groupRole) { 224 case GroupEvent.GROUP_OWNER: 225 sb.append("OWNER"); 226 break; 227 case GroupEvent.GROUP_CLIENT: 228 sb.append("CLIENT"); 229 break; 230 default: 231 sb.append("UNKNOWN DURING CONNECT"); 232 break; 233 } 234 sb.append(", connectivityLevelFailureCode="); 235 switch (event.connectivityLevelFailureCode) { 236 case P2pConnectionEvent.CLF_NONE: 237 sb.append("NONE"); 238 break; 239 case P2pConnectionEvent.CLF_TIMEOUT: 240 sb.append("TIMEOUT"); 241 break; 242 case P2pConnectionEvent.CLF_CANCEL: 243 sb.append("CANCEL"); 244 break; 245 case P2pConnectionEvent.CLF_PROV_DISC_FAIL: 246 sb.append("PROV_DISC_FAIL"); 247 break; 248 case P2pConnectionEvent.CLF_INVITATION_FAIL: 249 sb.append("INVITATION_FAIL"); 250 break; 251 case P2pConnectionEvent.CLF_USER_REJECT: 252 sb.append("USER_REJECT"); 253 break; 254 case P2pConnectionEvent.CLF_NEW_CONNECTION_ATTEMPT: 255 sb.append("NEW_CONNECTION_ATTEMPT"); 256 break; 257 case P2pConnectionEvent.CLF_UNKNOWN: 258 default: 259 sb.append("UNKNOWN"); 260 break; 261 } 262 263 if (event == mCurrentConnectionEvent) { 264 sb.append(" CURRENTLY OPEN EVENT"); 265 } 266 pw.println(sb.toString()); 267 } 268 pw.println("mGroupEvents:"); 269 for (GroupEvent event : mGroupEventList) { 270 StringBuilder sb = new StringBuilder(); 271 Calendar c = Calendar.getInstance(); 272 c.setTimeInMillis(event.startTimeMillis); 273 sb.append("netId="); 274 sb.append(event.netId); 275 sb.append(", startTime="); 276 sb.append(event.startTimeMillis == 0 ? " <null>" : 277 String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); 278 sb.append(", channelFrequency="); 279 sb.append(event.channelFrequency); 280 sb.append(", groupRole="); 281 switch (event.groupRole) { 282 case GroupEvent.GROUP_CLIENT: 283 sb.append("GroupClient"); 284 break; 285 case GroupEvent.GROUP_OWNER: 286 default: 287 sb.append("GroupOwner"); 288 break; 289 } 290 sb.append(", numConnectedClients="); 291 sb.append(event.numConnectedClients); 292 sb.append(", numCumulativeClients="); 293 sb.append(event.numCumulativeClients); 294 sb.append(", sessionDurationMillis="); 295 sb.append(event.sessionDurationMillis); 296 sb.append(", idleDurationMillis="); 297 sb.append(event.idleDurationMillis); 298 299 if (event == mCurrentGroupEvent) { 300 sb.append(" CURRENTLY OPEN EVENT"); 301 } 302 pw.println(sb.toString()); 303 } 304 pw.println("mWifiP2pStatsProto.numPersistentGroup=" 305 + mNumPersistentGroup); 306 pw.println("mWifiP2pStatsProto.numTotalPeerScans=" 307 + mWifiP2pStatsProto.numTotalPeerScans); 308 pw.println("mWifiP2pStatsProto.numTotalServiceScans=" 309 + mWifiP2pStatsProto.numTotalServiceScans); 310 } 311 } 312 313 /** Increment total number of peer scans */ incrementPeerScans()314 public void incrementPeerScans() { 315 synchronized (mLock) { 316 mWifiP2pStatsProto.numTotalPeerScans++; 317 } 318 } 319 320 /** Increment total number of service scans */ incrementServiceScans()321 public void incrementServiceScans() { 322 synchronized (mLock) { 323 mWifiP2pStatsProto.numTotalServiceScans++; 324 } 325 } 326 327 /** Set the number of saved persistent group */ updatePersistentGroup(WifiP2pGroupList groups)328 public void updatePersistentGroup(WifiP2pGroupList groups) { 329 synchronized (mLock) { 330 final Collection<WifiP2pGroup> list = groups.getGroupList(); 331 mNumPersistentGroup = list.size(); 332 } 333 } 334 335 /** 336 * Create a new connection event. Call when p2p attempts to make a new connection to 337 * another peer. If there is a current 'un-ended' connection event, it will be ended with 338 * P2pConnectionEvent.CLF_NEW_CONNECTION_ATTEMPT. 339 * 340 * @param connectionType indicate this connection is fresh or reinvoke. 341 * @param config configuration used for this connection. 342 * @param groupRole groupRole used for this connection. 343 */ startConnectionEvent(int connectionType, WifiP2pConfig config, int groupRole)344 public void startConnectionEvent(int connectionType, WifiP2pConfig config, int groupRole) { 345 synchronized (mLock) { 346 // handle overlapping connection event first. 347 if (mCurrentConnectionEvent != null) { 348 endConnectionEvent(P2pConnectionEvent.CLF_NEW_CONNECTION_ATTEMPT); 349 } 350 351 while (mConnectionEventList.size() >= MAX_CONNECTION_EVENTS) { 352 mConnectionEventList.remove(0); 353 } 354 mCurrentConnectionEventStartTime = mClock.getElapsedSinceBootMillis(); 355 356 mCurrentConnectionEvent = new P2pConnectionEvent(); 357 mCurrentConnectionEvent.startTimeMillis = mClock.getWallClockMillis(); 358 mCurrentConnectionEvent.connectionType = connectionType; 359 mCurrentConnectionEvent.groupRole = groupRole; 360 if (config != null) { 361 mCurrentConnectionEvent.wpsMethod = config.wps.setup; 362 } 363 364 mConnectionEventList.add(mCurrentConnectionEvent); 365 } 366 } 367 368 /** 369 * End a Connection event record. Call when p2p connection attempt succeeds or fails. 370 * If a Connection event has not been started when .end is called, 371 * a new one is created with zero duration. 372 * 373 * @param failure indicate the failure with WifiMetricsProto.P2pConnectionEvent.CLF_X. 374 */ endConnectionEvent(int failure)375 public void endConnectionEvent(int failure) { 376 synchronized (mLock) { 377 if (mCurrentConnectionEvent == null) { 378 // Reinvoking a group with invitation will be handled in supplicant. 379 // There won't be a connection starting event in framework. 380 // THe framework only get the connection ending event in GroupStarted state. 381 startConnectionEvent(P2pConnectionEvent.CONNECTION_REINVOKE, null, 382 GroupEvent.GROUP_UNKNOWN); 383 } 384 385 mCurrentConnectionEvent.durationTakenToConnectMillis = (int) 386 (mClock.getElapsedSinceBootMillis() 387 - mCurrentConnectionEventStartTime); 388 mCurrentConnectionEvent.connectivityLevelFailureCode = failure; 389 390 WifiStatsLog.write(WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED, 391 convertConnectionType(mCurrentConnectionEvent.connectionType), 392 mCurrentConnectionEvent.durationTakenToConnectMillis, 393 mCurrentConnectionEvent.durationTakenToConnectMillis / 200, 394 convertFailureCode(failure), 395 convertGroupRole(mCurrentConnectionEvent.groupRole)); 396 mCurrentConnectionEvent = null; 397 } 398 } 399 convertConnectionType(int connectionType)400 private int convertConnectionType(int connectionType) { 401 switch (connectionType) { 402 case P2pConnectionEvent.CONNECTION_FRESH: 403 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__TYPE__FRESH; 404 case P2pConnectionEvent.CONNECTION_REINVOKE: 405 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__TYPE__REINVOKE; 406 case P2pConnectionEvent.CONNECTION_LOCAL: 407 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__TYPE__LOCAL; 408 case P2pConnectionEvent.CONNECTION_FAST: 409 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__TYPE__FAST; 410 default: 411 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__TYPE__UNSPECIFIED; 412 } 413 } 414 convertFailureCode(int failureCode)415 private int convertFailureCode(int failureCode) { 416 switch (failureCode) { 417 case P2pConnectionEvent.CLF_NONE: 418 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__FAILURE_CODE__NONE; 419 case P2pConnectionEvent.CLF_TIMEOUT: 420 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__FAILURE_CODE__TIMEOUT; 421 case P2pConnectionEvent.CLF_CANCEL: 422 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__FAILURE_CODE__CANCEL; 423 case P2pConnectionEvent.CLF_PROV_DISC_FAIL: 424 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__FAILURE_CODE__PROV_DISC_FAIL; 425 case P2pConnectionEvent.CLF_INVITATION_FAIL: 426 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__FAILURE_CODE__INVITATION_FAIL; 427 case P2pConnectionEvent.CLF_USER_REJECT: 428 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__FAILURE_CODE__USER_REJECT; 429 case P2pConnectionEvent.CLF_NEW_CONNECTION_ATTEMPT: 430 return WifiStatsLog 431 .WIFI_P2P_CONNECTION_REPORTED__FAILURE_CODE__NEW_CONNECTION_ATTEMPT; 432 case P2pConnectionEvent.CLF_UNKNOWN: 433 default: 434 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__FAILURE_CODE__UNKNOWN; 435 } 436 } 437 convertGroupRole(int groupRole)438 private int convertGroupRole(int groupRole) { 439 switch (groupRole) { 440 case GroupEvent.GROUP_OWNER: 441 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__GROUP_ROLE__GROUP_OWNER; 442 case GroupEvent.GROUP_CLIENT: 443 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__GROUP_ROLE__GROUP_CLIENT; 444 default: 445 return WifiStatsLog.WIFI_P2P_CONNECTION_REPORTED__GROUP_ROLE__GROUP_UNKNOWN; 446 } 447 } 448 449 /** 450 * Create a new group event. 451 * 452 * @param group the information of started group. 453 */ startGroupEvent(WifiP2pGroup group)454 public void startGroupEvent(WifiP2pGroup group) { 455 if (group == null) { 456 if (DBG) Log.d(TAG, "Cannot start group event due to null group"); 457 return; 458 } 459 synchronized (mLock) { 460 // handle overlapping group event first. 461 if (mCurrentGroupEvent != null) { 462 if (DBG) Log.d(TAG, "Overlapping group event!"); 463 endGroupEvent(); 464 } 465 466 while (mGroupEventList.size() >= MAX_GROUP_EVENTS) { 467 mGroupEventList.remove(0); 468 } 469 mCurrentGroupEventStartTime = mClock.getElapsedSinceBootMillis(); 470 if (group.getClientList().size() == 0) { 471 mCurrentGroupEventIdleStartTime = mClock.getElapsedSinceBootMillis(); 472 } else { 473 mCurrentGroupEventIdleStartTime = 0; 474 } 475 476 mCurrentGroupEvent = new GroupEvent(); 477 mCurrentGroupEvent.netId = group.getNetworkId(); 478 mCurrentGroupEvent.startTimeMillis = mClock.getWallClockMillis(); 479 mCurrentGroupEvent.numConnectedClients = group.getClientList().size(); 480 mCurrentGroupEvent.channelFrequency = group.getFrequency(); 481 mCurrentGroupEvent.groupRole = group.isGroupOwner() 482 ? GroupEvent.GROUP_OWNER 483 : GroupEvent.GROUP_CLIENT; 484 mGroupEventList.add(mCurrentGroupEvent); 485 } 486 } 487 488 /** 489 * Update the information of started group. 490 */ updateGroupEvent(WifiP2pGroup group)491 public void updateGroupEvent(WifiP2pGroup group) { 492 if (group == null) { 493 if (DBG) Log.d(TAG, "Cannot update group event due to null group."); 494 return; 495 } 496 synchronized (mLock) { 497 if (mCurrentGroupEvent == null) { 498 Log.w(TAG, "Cannot update group event due to no current group."); 499 return; 500 } 501 502 if (mCurrentGroupEvent.netId != group.getNetworkId()) { 503 Log.w(TAG, "Updating group id " + group.getNetworkId() 504 + " is different from current group id " + mCurrentGroupEvent.netId 505 + "."); 506 return; 507 } 508 509 int delta = group.getClientList().size() - mCurrentGroupEvent.numConnectedClients; 510 mCurrentGroupEvent.numConnectedClients = group.getClientList().size(); 511 if (delta > 0) { 512 mCurrentGroupEvent.numCumulativeClients += delta; 513 } 514 515 // if new client comes during idle period, cumulate idle duration and reset idle timer. 516 // if the last client disconnected during non-idle period, start idle timer. 517 if (mCurrentGroupEventIdleStartTime > 0) { 518 if (group.getClientList().size() > 0) { 519 mCurrentGroupEvent.idleDurationMillis += 520 (mClock.getElapsedSinceBootMillis() 521 - mCurrentGroupEventIdleStartTime); 522 mCurrentGroupEventIdleStartTime = 0; 523 } 524 } else { 525 if (group.getClientList().size() == 0) { 526 mCurrentGroupEventIdleStartTime = mClock.getElapsedSinceBootMillis(); 527 } 528 } 529 } 530 } 531 532 /** 533 * End a group event. 534 */ endGroupEvent()535 public void endGroupEvent() { 536 synchronized (mLock) { 537 if (mCurrentGroupEvent != null) { 538 mCurrentGroupEvent.sessionDurationMillis = (int) 539 (mClock.getElapsedSinceBootMillis() 540 - mCurrentGroupEventStartTime); 541 if (mCurrentGroupEventIdleStartTime > 0) { 542 mCurrentGroupEvent.idleDurationMillis += 543 (mClock.getElapsedSinceBootMillis() 544 - mCurrentGroupEventIdleStartTime); 545 mCurrentGroupEventIdleStartTime = 0; 546 } 547 } else { 548 Log.e(TAG, "No current group!"); 549 } 550 mCurrentGroupEvent = null; 551 } 552 } 553 554 /* Log Metrics */ 555 } 556