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