| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.wifi.p2p; |
| |
| import android.net.wifi.p2p.WifiP2pConfig; |
| import android.net.wifi.p2p.WifiP2pGroup; |
| import android.net.wifi.p2p.WifiP2pGroupList; |
| import android.util.Log; |
| |
| import com.android.server.wifi.Clock; |
| import com.android.server.wifi.proto.nano.WifiMetricsProto.GroupEvent; |
| import com.android.server.wifi.proto.nano.WifiMetricsProto.P2pConnectionEvent; |
| import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiP2pStats; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.List; |
| |
| /** |
| * Provides storage for wireless connectivity P2p metrics, as they are generated. |
| * Metrics logged by this class include: |
| * Aggregated connection stats (num of connections, num of failures, ...) |
| * Discrete connection event stats (time, duration, failure codes, ...) |
| */ |
| public class WifiP2pMetrics { |
| private static final String TAG = "WifiP2pMetrics"; |
| private static final boolean DBG = false; |
| |
| private static final int MAX_CONNECTION_EVENTS = 256; |
| private static final int MAX_GROUP_EVENTS = 256; |
| |
| private Clock mClock; |
| private final Object mLock = new Object(); |
| |
| /** |
| * Metrics are stored within an instance of the WifiP2pStats proto during runtime, |
| * The P2pConnectionEvent and GroupEvent metrics are stored during runtime in member |
| * lists of this WifiP2pMetrics class, with the final WifiLog proto being pieced |
| * together at dump-time |
| */ |
| private final WifiP2pStats mWifiP2pStatsProto = |
| new WifiP2pStats(); |
| |
| /** |
| * Connection information that gets logged for every P2P connection attempt. |
| */ |
| private final List<P2pConnectionEvent> mConnectionEventList = |
| new ArrayList<>(); |
| |
| /** |
| * The latest started (but un-ended) connection attempt |
| */ |
| private P2pConnectionEvent mCurrentConnectionEvent; |
| |
| /** |
| * The latest started (but un-ended) connection attempt start time |
| */ |
| private long mCurrentConnectionEventStartTime; |
| |
| /** |
| * Group Session information that gets logged for every formed group. |
| */ |
| private final List<GroupEvent> mGroupEventList = |
| new ArrayList<>(); |
| |
| /** |
| * The latest started (but un-ended) group |
| */ |
| private GroupEvent mCurrentGroupEvent; |
| |
| /** |
| * The latest started (but un-ended) group start time |
| */ |
| private long mCurrentGroupEventStartTime; |
| |
| /** |
| * The latest started (but un-ended) group idle start time. |
| * The group is idle if there is no connected client. |
| */ |
| private long mCurrentGroupEventIdleStartTime; |
| |
| /** |
| * The current number of persistent groups. |
| * This should be persisted after a dump. |
| */ |
| private int mNumPersistentGroup; |
| |
| public WifiP2pMetrics(Clock clock) { |
| mClock = clock; |
| |
| mNumPersistentGroup = 0; |
| } |
| |
| /** |
| * Clear all WifiP2pMetrics, except for currentConnectionEvent. |
| */ |
| public void clear() { |
| synchronized (mLock) { |
| mConnectionEventList.clear(); |
| if (mCurrentConnectionEvent != null) { |
| mConnectionEventList.add(mCurrentConnectionEvent); |
| } |
| mGroupEventList.clear(); |
| if (mCurrentGroupEvent != null) { |
| mGroupEventList.add(mCurrentGroupEvent); |
| } |
| mWifiP2pStatsProto.clear(); |
| } |
| } |
| |
| /** |
| * Put all metrics that were being tracked separately into mWifiP2pStatsProto |
| */ |
| public WifiP2pStats consolidateProto() { |
| synchronized (mLock) { |
| mWifiP2pStatsProto.numPersistentGroup = mNumPersistentGroup; |
| int connectionEventCount = mConnectionEventList.size(); |
| if (mCurrentConnectionEvent != null) { |
| connectionEventCount--; |
| } |
| mWifiP2pStatsProto.connectionEvent = |
| new P2pConnectionEvent[connectionEventCount]; |
| for (int i = 0; i < connectionEventCount; i++) { |
| mWifiP2pStatsProto.connectionEvent[i] = mConnectionEventList.get(i); |
| } |
| |
| int groupEventCount = mGroupEventList.size(); |
| if (mCurrentGroupEvent != null) { |
| groupEventCount--; |
| } |
| mWifiP2pStatsProto.groupEvent = |
| new GroupEvent[groupEventCount]; |
| for (int i = 0; i < groupEventCount; i++) { |
| mWifiP2pStatsProto.groupEvent[i] = mGroupEventList.get(i); |
| } |
| return mWifiP2pStatsProto; |
| } |
| } |
| |
| /** |
| * Dump all WifiP2pMetrics. Collects some metrics at this time. |
| * |
| * @param pw PrintWriter for writing dump to |
| */ |
| public void dump(PrintWriter pw) { |
| synchronized (mLock) { |
| pw.println("WifiP2pMetrics:"); |
| pw.println("mConnectionEvents:"); |
| for (P2pConnectionEvent event : mConnectionEventList) { |
| StringBuilder sb = new StringBuilder(); |
| Calendar c = Calendar.getInstance(); |
| c.setTimeInMillis(event.startTimeMillis); |
| sb.append("startTime="); |
| sb.append(event.startTimeMillis == 0 ? " <null>" : |
| String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); |
| sb.append(", connectionType="); |
| switch (event.connectionType) { |
| case P2pConnectionEvent.CONNECTION_FRESH: |
| sb.append("FRESH"); |
| break; |
| case P2pConnectionEvent.CONNECTION_REINVOKE: |
| sb.append("REINVOKE"); |
| break; |
| case P2pConnectionEvent.CONNECTION_LOCAL: |
| sb.append("LOCAL"); |
| break; |
| case P2pConnectionEvent.CONNECTION_FAST: |
| sb.append("FAST"); |
| break; |
| default: |
| sb.append("UNKNOWN"); |
| break; |
| } |
| sb.append(", wpsMethod="); |
| switch (event.wpsMethod) { |
| case P2pConnectionEvent.WPS_NA: |
| sb.append("NA"); |
| break; |
| case P2pConnectionEvent.WPS_PBC: |
| sb.append("PBC"); |
| break; |
| case P2pConnectionEvent.WPS_DISPLAY: |
| sb.append("DISPLAY"); |
| break; |
| case P2pConnectionEvent.WPS_KEYPAD: |
| sb.append("KEYPAD"); |
| break; |
| case P2pConnectionEvent.WPS_LABEL: |
| sb.append("LABLE"); |
| break; |
| default: |
| sb.append("UNKNOWN"); |
| break; |
| } |
| sb.append(", durationTakenToConnectMillis="); |
| sb.append(event.durationTakenToConnectMillis); |
| sb.append(", connectivityLevelFailureCode="); |
| switch (event.connectivityLevelFailureCode) { |
| case P2pConnectionEvent.CLF_NONE: |
| sb.append("NONE"); |
| break; |
| case P2pConnectionEvent.CLF_TIMEOUT: |
| sb.append("TIMEOUT"); |
| break; |
| case P2pConnectionEvent.CLF_CANCEL: |
| sb.append("CANCEL"); |
| break; |
| case P2pConnectionEvent.CLF_PROV_DISC_FAIL: |
| sb.append("PROV_DISC_FAIL"); |
| break; |
| case P2pConnectionEvent.CLF_INVITATION_FAIL: |
| sb.append("INVITATION_FAIL"); |
| break; |
| case P2pConnectionEvent.CLF_USER_REJECT: |
| sb.append("USER_REJECT"); |
| break; |
| case P2pConnectionEvent.CLF_NEW_CONNECTION_ATTEMPT: |
| sb.append("NEW_CONNECTION_ATTEMPT"); |
| break; |
| case P2pConnectionEvent.CLF_UNKNOWN: |
| default: |
| sb.append("UNKNOWN"); |
| break; |
| } |
| |
| if (event == mCurrentConnectionEvent) { |
| sb.append(" CURRENTLY OPEN EVENT"); |
| } |
| pw.println(sb.toString()); |
| } |
| pw.println("mGroupEvents:"); |
| for (GroupEvent event : mGroupEventList) { |
| StringBuilder sb = new StringBuilder(); |
| Calendar c = Calendar.getInstance(); |
| c.setTimeInMillis(event.startTimeMillis); |
| sb.append("netId="); |
| sb.append(event.netId); |
| sb.append(", startTime="); |
| sb.append(event.startTimeMillis == 0 ? " <null>" : |
| String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); |
| sb.append(", channelFrequency="); |
| sb.append(event.channelFrequency); |
| sb.append(", groupRole="); |
| switch (event.groupRole) { |
| case GroupEvent.GROUP_CLIENT: |
| sb.append("GroupClient"); |
| break; |
| case GroupEvent.GROUP_OWNER: |
| default: |
| sb.append("GroupOwner"); |
| break; |
| } |
| sb.append(", numConnectedClients="); |
| sb.append(event.numConnectedClients); |
| sb.append(", numCumulativeClients="); |
| sb.append(event.numCumulativeClients); |
| sb.append(", sessionDurationMillis="); |
| sb.append(event.sessionDurationMillis); |
| sb.append(", idleDurationMillis="); |
| sb.append(event.idleDurationMillis); |
| |
| if (event == mCurrentGroupEvent) { |
| sb.append(" CURRENTLY OPEN EVENT"); |
| } |
| pw.println(sb.toString()); |
| } |
| pw.println("mWifiP2pStatsProto.numPersistentGroup=" |
| + mNumPersistentGroup); |
| pw.println("mWifiP2pStatsProto.numTotalPeerScans=" |
| + mWifiP2pStatsProto.numTotalPeerScans); |
| pw.println("mWifiP2pStatsProto.numTotalServiceScans=" |
| + mWifiP2pStatsProto.numTotalServiceScans); |
| } |
| } |
| |
| /** Increment total number of peer scans */ |
| public void incrementPeerScans() { |
| synchronized (mLock) { |
| mWifiP2pStatsProto.numTotalPeerScans++; |
| } |
| } |
| |
| /** Increment total number of service scans */ |
| public void incrementServiceScans() { |
| synchronized (mLock) { |
| mWifiP2pStatsProto.numTotalServiceScans++; |
| } |
| } |
| |
| /** Set the number of saved persistent group */ |
| public void updatePersistentGroup(WifiP2pGroupList groups) { |
| synchronized (mLock) { |
| final Collection<WifiP2pGroup> list = groups.getGroupList(); |
| mNumPersistentGroup = list.size(); |
| } |
| } |
| |
| /** |
| * Create a new connection event. Call when p2p attmpts to make a new connection to |
| * another peer. If there is a current 'un-ended' connection event, it will be ended with |
| * P2pConnectionEvent.CLF_NEW_CONNNECTION_ATTEMPT. |
| * |
| * @param connectionType indicate this connection is fresh or reinvoke. |
| * @param config configuration used for this connection. |
| */ |
| public void startConnectionEvent(int connectionType, WifiP2pConfig config) { |
| synchronized (mLock) { |
| // handle overlapping connection event first. |
| if (mCurrentConnectionEvent != null) { |
| endConnectionEvent(P2pConnectionEvent.CLF_NEW_CONNECTION_ATTEMPT); |
| } |
| |
| while (mConnectionEventList.size() >= MAX_CONNECTION_EVENTS) { |
| mConnectionEventList.remove(0); |
| } |
| mCurrentConnectionEventStartTime = mClock.getElapsedSinceBootMillis(); |
| |
| mCurrentConnectionEvent = new P2pConnectionEvent(); |
| mCurrentConnectionEvent.startTimeMillis = mClock.getWallClockMillis(); |
| mCurrentConnectionEvent.connectionType = connectionType; |
| if (config != null) { |
| mCurrentConnectionEvent.wpsMethod = config.wps.setup; |
| } |
| |
| mConnectionEventList.add(mCurrentConnectionEvent); |
| } |
| } |
| |
| /** |
| * End a Connection event record. Call when p2p connection attempt succeeds or fails. |
| * If a Connection event has not been started when .end is called, |
| * a new one is created with zero duration. |
| * |
| * @param failure indicate the failure with WifiMetricsProto.P2pConnectionEvent.CLF_X. |
| */ |
| public void endConnectionEvent(int failure) { |
| synchronized (mLock) { |
| if (mCurrentConnectionEvent == null) { |
| // Reinvoking a group with invitation will be handled in supplicant. |
| // There won't be a connection starting event in framework. |
| // THe framework only get the connection ending event in GroupStarted state. |
| startConnectionEvent(P2pConnectionEvent.CONNECTION_REINVOKE, null); |
| } |
| |
| mCurrentConnectionEvent.durationTakenToConnectMillis = (int) |
| (mClock.getElapsedSinceBootMillis() |
| - mCurrentConnectionEventStartTime); |
| mCurrentConnectionEvent.connectivityLevelFailureCode = failure; |
| |
| mCurrentConnectionEvent = null; |
| } |
| } |
| |
| /** |
| * Create a new group event. |
| * |
| * @param group the information of started group. |
| */ |
| public void startGroupEvent(WifiP2pGroup group) { |
| if (group == null) { |
| if (DBG) Log.d(TAG, "Cannot start group event due to null group"); |
| return; |
| } |
| synchronized (mLock) { |
| // handle overlapping group event first. |
| if (mCurrentGroupEvent != null) { |
| if (DBG) Log.d(TAG, "Overlapping group event!"); |
| endGroupEvent(); |
| } |
| |
| while (mGroupEventList.size() >= MAX_GROUP_EVENTS) { |
| mGroupEventList.remove(0); |
| } |
| mCurrentGroupEventStartTime = mClock.getElapsedSinceBootMillis(); |
| if (group.getClientList().size() == 0) { |
| mCurrentGroupEventIdleStartTime = mClock.getElapsedSinceBootMillis(); |
| } else { |
| mCurrentGroupEventIdleStartTime = 0; |
| } |
| |
| mCurrentGroupEvent = new GroupEvent(); |
| mCurrentGroupEvent.netId = group.getNetworkId(); |
| mCurrentGroupEvent.startTimeMillis = mClock.getWallClockMillis(); |
| mCurrentGroupEvent.numConnectedClients = group.getClientList().size(); |
| mCurrentGroupEvent.channelFrequency = group.getFrequency(); |
| mCurrentGroupEvent.groupRole = group.isGroupOwner() |
| ? GroupEvent.GROUP_OWNER |
| : GroupEvent.GROUP_CLIENT; |
| mGroupEventList.add(mCurrentGroupEvent); |
| } |
| } |
| |
| /** |
| * Update the information of started group. |
| */ |
| public void updateGroupEvent(WifiP2pGroup group) { |
| if (group == null) { |
| if (DBG) Log.d(TAG, "Cannot update group event due to null group."); |
| return; |
| } |
| synchronized (mLock) { |
| if (mCurrentGroupEvent == null) { |
| Log.w(TAG, "Cannot update group event due to no current group."); |
| return; |
| } |
| |
| if (mCurrentGroupEvent.netId != group.getNetworkId()) { |
| Log.w(TAG, "Updating group id " + group.getNetworkId() |
| + " is different from current group id " + mCurrentGroupEvent.netId |
| + "."); |
| return; |
| } |
| |
| int delta = group.getClientList().size() - mCurrentGroupEvent.numConnectedClients; |
| mCurrentGroupEvent.numConnectedClients = group.getClientList().size(); |
| if (delta > 0) { |
| mCurrentGroupEvent.numCumulativeClients += delta; |
| } |
| |
| // if new client comes during idle period, cumulate idle duration and reset idle timer. |
| // if the last client disconnected during non-idle period, start idle timer. |
| if (mCurrentGroupEventIdleStartTime > 0) { |
| if (group.getClientList().size() > 0) { |
| mCurrentGroupEvent.idleDurationMillis += |
| (mClock.getElapsedSinceBootMillis() |
| - mCurrentGroupEventIdleStartTime); |
| mCurrentGroupEventIdleStartTime = 0; |
| } |
| } else { |
| if (group.getClientList().size() == 0) { |
| mCurrentGroupEventIdleStartTime = mClock.getElapsedSinceBootMillis(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * End a group event. |
| */ |
| public void endGroupEvent() { |
| synchronized (mLock) { |
| if (mCurrentGroupEvent != null) { |
| mCurrentGroupEvent.sessionDurationMillis = (int) |
| (mClock.getElapsedSinceBootMillis() |
| - mCurrentGroupEventStartTime); |
| if (mCurrentGroupEventIdleStartTime > 0) { |
| mCurrentGroupEvent.idleDurationMillis += |
| (mClock.getElapsedSinceBootMillis() |
| - mCurrentGroupEventIdleStartTime); |
| mCurrentGroupEventIdleStartTime = 0; |
| } |
| } else { |
| Log.e(TAG, "No current group!"); |
| } |
| mCurrentGroupEvent = null; |
| } |
| } |
| |
| /* Log Metrics */ |
| } |