/* * 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 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 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 ? " " : 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 ? " " : 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 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 */ }