diff options
5 files changed, 664 insertions, 9 deletions
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 3d444a448..a977a4693 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -88,7 +88,7 @@ public class WifiInjector { private final WifiController mWifiController; private final WificondControl mWificondControl; private final Clock mClock = new Clock(); - private final WifiMetrics mWifiMetrics = new WifiMetrics(mClock); + private final WifiMetrics mWifiMetrics; private final WifiLastResortWatchdog mWifiLastResortWatchdog; private final PropertyService mPropertyService = new SystemPropertyService(); private final BuildProperties mBuildProperties = new SystemBuildProperties(); @@ -151,7 +151,7 @@ public class WifiInjector { mWifiStateMachineHandlerThread = new HandlerThread("WifiStateMachine"); mWifiStateMachineHandlerThread.start(); Looper wifiStateMachineLooper = mWifiStateMachineHandlerThread.getLooper(); - + mWifiMetrics = new WifiMetrics(mClock, wifiStateMachineLooper); // Modules interacting with Native. mWifiMonitor = new WifiMonitor(this); mHalDeviceManager = new HalDeviceManager(); diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java index 934a4c64f..411d7d7e1 100644 --- a/service/java/com/android/server/wifi/WifiMetrics.java +++ b/service/java/com/android/server/wifi/WifiMetrics.java @@ -16,23 +16,33 @@ package com.android.server.wifi; +import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback; import android.net.NetworkAgent; import android.net.wifi.ScanResult; +import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.Base64; import android.util.Log; import android.util.SparseIntArray; import com.android.server.wifi.hotspot2.NetworkDetail; import com.android.server.wifi.nano.WifiMetricsProto; +import com.android.server.wifi.nano.WifiMetricsProto.StaEvent; +import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo; import com.android.server.wifi.util.InformationElementUtil; import com.android.server.wifi.util.ScanResultUtil; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.BitSet; import java.util.Calendar; +import java.util.LinkedList; import java.util.List; /** @@ -62,6 +72,7 @@ public class WifiMetrics { private Clock mClock; private boolean mScreenOn; private int mWifiState; + private Handler mHandler; /** * Metrics are stored within an instance of the WifiLog proto during runtime, * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during @@ -333,12 +344,20 @@ public class WifiMetrics { } } - public WifiMetrics(Clock clock) { + public WifiMetrics(Clock clock, Looper looper) { mClock = clock; mCurrentConnectionEvent = null; mScreenOn = true; mWifiState = WifiMetricsProto.WifiLog.WIFI_DISABLED; mRecordStartTimeSec = mClock.getElapsedSinceBootMillis() / 1000; + + mHandler = new Handler(looper) { + public void handleMessage(Message msg) { + synchronized (mLock) { + processMessage(msg); + } + } + }; } // Values used for indexing SystemStateEntries @@ -796,6 +815,16 @@ public class WifiMetrics { } /** + * Increment various poll related metrics, and cache performance data for StaEvent logging + */ + public void handlePollResult(WifiInfo wifiInfo) { + mLastPollRssi = wifiInfo.getRssi(); + mLastPollLinkSpeed = wifiInfo.getLinkSpeed(); + mLastPollFreq = wifiInfo.getFrequency(); + incrementRssiPollRssiCount(mLastPollRssi); + } + + /** * Increment occurence count of RSSI level from RSSI poll. * Ignores rssi values outside the bounds of [MIN_RSSI_POLL, MAX_RSSI_POLL] */ @@ -1146,6 +1175,10 @@ public class WifiMetrics { pw.println(" FAILED_NO_CHANNEL: " + mSoftApManagerReturnCodeCounts.get( WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_NO_CHANNEL)); pw.print("\n"); + pw.println("StaEventList:"); + for (StaEvent event : mStaEventList) { + pw.println(staEventToString(event)); + } } } } @@ -1308,6 +1341,8 @@ public class WifiMetrics { mWifiLogProto.softApReturnCode[sapCode].count = mSoftApManagerReturnCodeCounts.valueAt(sapCode); } + + mWifiLogProto.staEventList = mStaEventList.toArray(mWifiLogProto.staEventList); } } @@ -1330,6 +1365,7 @@ public class WifiMetrics { mWifiLogProto.clear(); mScanResultRssiTimestampMillis = -1; mSoftApManagerReturnCodeCounts.clear(); + mStaEventList.clear(); } } @@ -1350,4 +1386,370 @@ public class WifiMetrics { mWifiState = wifiState; } } + + /** + * Message handler for interesting WifiMonitor messages. Generates StaEvents + */ + private void processMessage(Message msg) { + StaEvent event = new StaEvent(); + boolean logEvent = true; + switch (msg.what) { + case WifiMonitor.ASSOCIATION_REJECTION_EVENT: + event.type = StaEvent.TYPE_ASSOCIATION_REJECTION_EVENT; + event.associationTimedOut = msg.arg1 > 0 ? true : false; + event.status = msg.arg2; + break; + case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: + event.type = StaEvent.TYPE_AUTHENTICATION_FAILURE_EVENT; + switch (msg.arg2) { + case WifiManager.ERROR_AUTH_FAILURE_NONE: + event.authFailureReason = StaEvent.AUTH_FAILURE_NONE; + break; + case WifiManager.ERROR_AUTH_FAILURE_TIMEOUT: + event.authFailureReason = StaEvent.AUTH_FAILURE_TIMEOUT; + break; + case WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD: + event.authFailureReason = StaEvent.AUTH_FAILURE_WRONG_PSWD; + break; + case WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE: + event.authFailureReason = StaEvent.AUTH_FAILURE_EAP_FAILURE; + break; + default: + break; + } + break; + case WifiMonitor.NETWORK_CONNECTION_EVENT: + event.type = StaEvent.TYPE_NETWORK_CONNECTION_EVENT; + break; + case WifiMonitor.NETWORK_DISCONNECTION_EVENT: + event.type = StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT; + event.reason = msg.arg2; + event.localGen = msg.arg1 == 0 ? false : true; + break; + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: + logEvent = false; + StateChangeResult stateChangeResult = (StateChangeResult) msg.obj; + mSupplicantStateChangeBitmask |= supplicantStateToBit(stateChangeResult.state); + break; + case WifiStateMachine.CMD_ASSOCIATED_BSSID: + event.type = StaEvent.TYPE_CMD_ASSOCIATED_BSSID; + break; + case WifiStateMachine.CMD_TARGET_BSSID: + event.type = StaEvent.TYPE_CMD_TARGET_BSSID; + break; + default: + return; + } + if (logEvent) { + addStaEvent(event); + } + } + /** + * Log a StaEvent from WifiStateMachine. The StaEvent must not be one of the supplicant + * generated event types, which are logged through 'sendMessage' + * @param type StaEvent.EventType describing the event + */ + public void logStaEvent(int type) { + logStaEvent(type, StaEvent.DISCONNECT_UNKNOWN, null); + } + /** + * Log a StaEvent from WifiStateMachine. The StaEvent must not be one of the supplicant + * generated event types, which are logged through 'sendMessage' + * @param type StaEvent.EventType describing the event + * @param config WifiConfiguration for a framework initiated connection attempt + */ + public void logStaEvent(int type, WifiConfiguration config) { + logStaEvent(type, StaEvent.DISCONNECT_UNKNOWN, config); + } + /** + * Log a StaEvent from WifiStateMachine. The StaEvent must not be one of the supplicant + * generated event types, which are logged through 'sendMessage' + * @param type StaEvent.EventType describing the event + * @param frameworkDisconnectReason StaEvent.FrameworkDisconnectReason explaining why framework + * initiated a FRAMEWORK_DISCONNECT + */ + public void logStaEvent(int type, int frameworkDisconnectReason) { + logStaEvent(type, frameworkDisconnectReason, null); + } + /** + * Log a StaEvent from WifiStateMachine. The StaEvent must not be one of the supplicant + * generated event types, which are logged through 'sendMessage' + * @param type StaEvent.EventType describing the event + * @param frameworkDisconnectReason StaEvent.FrameworkDisconnectReason explaining why framework + * initiated a FRAMEWORK_DISCONNECT + * @param config WifiConfiguration for a framework initiated connection attempt + */ + public void logStaEvent(int type, int frameworkDisconnectReason, WifiConfiguration config) { + switch (type) { + case StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL: + case StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST: + case StaEvent.TYPE_CMD_IP_REACHABILITY_LOST: + case StaEvent.TYPE_CMD_START_CONNECT: + case StaEvent.TYPE_CMD_START_ROAM: + case StaEvent.TYPE_CONNECT_NETWORK: + case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK: + case StaEvent.TYPE_FRAMEWORK_DISCONNECT: + break; + default: + Log.e(TAG, "Unknown StaEvent:" + type); + return; + } + StaEvent event = new StaEvent(); + event.type = type; + if (frameworkDisconnectReason != StaEvent.DISCONNECT_UNKNOWN) { + event.frameworkDisconnectReason = frameworkDisconnectReason; + } + event.configInfo = createConfigInfo(config); + addStaEvent(event); + } + + private void addStaEvent(StaEvent staEvent) { + staEvent.startTimeMillis = mClock.getElapsedSinceBootMillis(); + staEvent.lastRssi = mLastPollRssi; + staEvent.lastFreq = mLastPollFreq; + staEvent.lastLinkSpeed = mLastPollLinkSpeed; + staEvent.supplicantStateChangesBitmask = mSupplicantStateChangeBitmask; + mSupplicantStateChangeBitmask = 0; + mLastPollRssi = -127; + mLastPollFreq = -1; + mLastPollLinkSpeed = -1; + mStaEventList.add(staEvent); + // Prune StaEventList if it gets too long + if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove(); + } + + private ConfigInfo createConfigInfo(WifiConfiguration config) { + if (config == null) return null; + ConfigInfo info = new ConfigInfo(); + info.allowedKeyManagement = bitSetToInt(config.allowedKeyManagement); + info.allowedProtocols = bitSetToInt(config.allowedProtocols); + info.allowedAuthAlgorithms = bitSetToInt(config.allowedAuthAlgorithms); + info.allowedPairwiseCiphers = bitSetToInt(config.allowedPairwiseCiphers); + info.allowedGroupCiphers = bitSetToInt(config.allowedGroupCiphers); + info.hiddenSsid = config.hiddenSSID; + info.isPasspoint = config.isPasspoint(); + info.isEphemeral = config.isEphemeral(); + info.hasEverConnected = config.getNetworkSelectionStatus().getHasEverConnected(); + ScanResult candidate = config.getNetworkSelectionStatus().getCandidate(); + if (candidate != null) { + info.scanRssi = candidate.level; + info.scanFreq = candidate.frequency; + } + return info; + } + + public Handler getHandler() { + return mHandler; + } + + // Rather than generate a StaEvent for each SUPPLICANT_STATE_CHANGE, cache these in a bitmask + // and attach it to the next event which is generated. + private int mSupplicantStateChangeBitmask = 0; + + /** + * Converts a SupplicantState value to a single bit, with position defined by + * {@code StaEvent.SupplicantState} + */ + public static int supplicantStateToBit(SupplicantState state) { + switch(state) { + case DISCONNECTED: + return 1 << StaEvent.STATE_DISCONNECTED; + case INTERFACE_DISABLED: + return 1 << StaEvent.STATE_INTERFACE_DISABLED; + case INACTIVE: + return 1 << StaEvent.STATE_INACTIVE; + case SCANNING: + return 1 << StaEvent.STATE_SCANNING; + case AUTHENTICATING: + return 1 << StaEvent.STATE_AUTHENTICATING; + case ASSOCIATING: + return 1 << StaEvent.STATE_ASSOCIATING; + case ASSOCIATED: + return 1 << StaEvent.STATE_ASSOCIATED; + case FOUR_WAY_HANDSHAKE: + return 1 << StaEvent.STATE_FOUR_WAY_HANDSHAKE; + case GROUP_HANDSHAKE: + return 1 << StaEvent.STATE_GROUP_HANDSHAKE; + case COMPLETED: + return 1 << StaEvent.STATE_COMPLETED; + case DORMANT: + return 1 << StaEvent.STATE_DORMANT; + case UNINITIALIZED: + return 1 << StaEvent.STATE_UNINITIALIZED; + case INVALID: + return 1 << StaEvent.STATE_INVALID; + default: + Log.wtf(TAG, "Got unknown supplicant state: " + state.ordinal()); + return 0; + } + } + + private static String supplicantStateChangesBitmaskToString(int mask) { + StringBuilder sb = new StringBuilder(); + sb.append("SUPPLICANT_STATE_CHANGE_EVENTS: {"); + if ((mask & (1 << StaEvent.STATE_DISCONNECTED)) > 0) sb.append(" DISCONNECTED"); + if ((mask & (1 << StaEvent.STATE_INTERFACE_DISABLED)) > 0) sb.append(" INTERFACE_DISABLED"); + if ((mask & (1 << StaEvent.STATE_INACTIVE)) > 0) sb.append(" INACTIVE"); + if ((mask & (1 << StaEvent.STATE_SCANNING)) > 0) sb.append(" SCANNING"); + if ((mask & (1 << StaEvent.STATE_AUTHENTICATING)) > 0) sb.append(" AUTHENTICATING"); + if ((mask & (1 << StaEvent.STATE_ASSOCIATING)) > 0) sb.append(" ASSOCIATING"); + if ((mask & (1 << StaEvent.STATE_ASSOCIATED)) > 0) sb.append(" ASSOCIATED"); + if ((mask & (1 << StaEvent.STATE_FOUR_WAY_HANDSHAKE)) > 0) sb.append(" FOUR_WAY_HANDSHAKE"); + if ((mask & (1 << StaEvent.STATE_GROUP_HANDSHAKE)) > 0) sb.append(" GROUP_HANDSHAKE"); + if ((mask & (1 << StaEvent.STATE_COMPLETED)) > 0) sb.append(" COMPLETED"); + if ((mask & (1 << StaEvent.STATE_DORMANT)) > 0) sb.append(" DORMANT"); + if ((mask & (1 << StaEvent.STATE_UNINITIALIZED)) > 0) sb.append(" UNINITIALIZED"); + if ((mask & (1 << StaEvent.STATE_INVALID)) > 0) sb.append(" INVALID"); + sb.append("}"); + return sb.toString(); + } + + /** + * Returns a human readable string from a Sta Event. Only adds information relevant to the event + * type. + */ + public static String staEventToString(StaEvent event) { + if (event == null) return "<NULL>"; + StringBuilder sb = new StringBuilder(); + Long time = event.startTimeMillis; + sb.append(String.format("%9d ", time.longValue())).append(" "); + switch (event.type) { + case StaEvent.TYPE_ASSOCIATION_REJECTION_EVENT: + sb.append("ASSOCIATION_REJECTION_EVENT:") + .append(" timedOut=").append(event.associationTimedOut) + .append(" status=").append(event.status).append(":") + .append(ISupplicantStaIfaceCallback.StatusCode.toString(event.status)); + break; + case StaEvent.TYPE_AUTHENTICATION_FAILURE_EVENT: + sb.append("AUTHENTICATION_FAILURE_EVENT: reason=").append(event.authFailureReason) + .append(":").append(authFailureReasonToString(event.authFailureReason)); + break; + case StaEvent.TYPE_NETWORK_CONNECTION_EVENT: + sb.append("NETWORK_CONNECTION_EVENT:"); + break; + case StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT: + sb.append("NETWORK_DISCONNECTION_EVENT:") + .append(" local_gen=").append(event.localGen) + .append(" reason=").append(event.reason).append(":") + .append(ISupplicantStaIfaceCallback.ReasonCode.toString( + (event.reason >= 0 ? event.reason : -1 * event.reason))); + break; + case StaEvent.TYPE_CMD_ASSOCIATED_BSSID: + sb.append("CMD_ASSOCIATED_BSSID:"); + break; + case StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL: + sb.append("CMD_IP_CONFIGURATION_SUCCESSFUL:"); + break; + case StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST: + sb.append("CMD_IP_CONFIGURATION_LOST:"); + break; + case StaEvent.TYPE_CMD_IP_REACHABILITY_LOST: + sb.append("CMD_IP_REACHABILITY_LOST:"); + break; + case StaEvent.TYPE_CMD_TARGET_BSSID: + sb.append("CMD_TARGET_BSSID:"); + break; + case StaEvent.TYPE_CMD_START_CONNECT: + sb.append("CMD_START_CONNECT:"); + break; + case StaEvent.TYPE_CMD_START_ROAM: + sb.append("CMD_START_ROAM:"); + break; + case StaEvent.TYPE_CONNECT_NETWORK: + sb.append("CONNECT_NETWORK:"); + break; + case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK: + sb.append("NETWORK_AGENT_VALID_NETWORK:"); + break; + case StaEvent.TYPE_FRAMEWORK_DISCONNECT: + sb.append("FRAMEWORK_DISCONNECT:") + .append(" reason=") + .append(frameworkDisconnectReasonToString(event.frameworkDisconnectReason)); + break; + default: + sb.append("UNKNOWN " + event.type + ":"); + break; + } + if (event.lastRssi != -127) sb.append(" lastRssi=").append(event.lastRssi); + if (event.lastFreq != -1) sb.append(" lastFreq=").append(event.lastFreq); + if (event.lastLinkSpeed != -1) sb.append(" lastLinkSpeed=").append(event.lastLinkSpeed); + if (event.supplicantStateChangesBitmask != 0) { + sb.append("\n ").append(supplicantStateChangesBitmaskToString( + event.supplicantStateChangesBitmask)); + } + if (event.configInfo != null) { + sb.append("\n ").append(configInfoToString(event.configInfo)); + } + + return sb.toString(); + } + + private static String authFailureReasonToString(int authFailureReason) { + switch (authFailureReason) { + case StaEvent.AUTH_FAILURE_NONE: + return "ERROR_AUTH_FAILURE_NONE"; + case StaEvent.AUTH_FAILURE_TIMEOUT: + return "ERROR_AUTH_FAILURE_TIMEOUT"; + case StaEvent.AUTH_FAILURE_WRONG_PSWD: + return "ERROR_AUTH_FAILURE_WRONG_PSWD"; + case StaEvent.AUTH_FAILURE_EAP_FAILURE: + return "ERROR_AUTH_FAILURE_EAP_FAILURE"; + default: + return ""; + } + } + + private static String frameworkDisconnectReasonToString(int frameworkDisconnectReason) { + switch (frameworkDisconnectReason) { + case StaEvent.DISCONNECT_API: + return "DISCONNECT_API"; + case StaEvent.DISCONNECT_GENERIC: + return "DISCONNECT_GENERIC"; + case StaEvent.DISCONNECT_UNWANTED: + return "DISCONNECT_UNWANTED"; + case StaEvent.DISCONNECT_ROAM_WATCHDOG_TIMER: + return "DISCONNECT_ROAM_WATCHDOG_TIMER"; + case StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST: + return "DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST"; + case StaEvent.DISCONNECT_RESET_SIM_NETWORKS: + return "DISCONNECT_RESET_SIM_NETWORKS"; + default: + return "DISCONNECT_UNKNOWN=" + frameworkDisconnectReason; + } + } + + private static String configInfoToString(ConfigInfo info) { + StringBuilder sb = new StringBuilder(); + sb.append("ConfigInfo:") + .append(" allowed_key_management=").append(info.allowedKeyManagement) + .append(" allowed_protocols=").append(info.allowedProtocols) + .append(" allowed_auth_algorithms=").append(info.allowedAuthAlgorithms) + .append(" allowed_pairwise_ciphers=").append(info.allowedPairwiseCiphers) + .append(" allowed_group_ciphers=").append(info.allowedGroupCiphers) + .append(" hidden_ssid=").append(info.hiddenSsid) + .append(" is_passpoint=").append(info.isPasspoint) + .append(" is_ephemeral=").append(info.isEphemeral) + .append(" has_ever_connected=").append(info.hasEverConnected) + .append(" scan_rssi=").append(info.scanRssi) + .append(" scan_freq=").append(info.scanFreq); + return sb.toString(); + } + + public static final int MAX_STA_EVENTS = 512; + private LinkedList<StaEvent> mStaEventList = new LinkedList<StaEvent>(); + private int mLastPollRssi = -127; + private int mLastPollLinkSpeed = -1; + private int mLastPollFreq = -1; + + /** + * Converts the first 31 bits of a BitSet to a little endian int + */ + private static int bitSetToInt(BitSet bits) { + int value = 0; + int nBits = bits.length() < 31 ? bits.length() : 31; + for (int i = 0; i < nBits; i++) { + value += bits.get(i) ? (1 << i) : 0; + } + return value; + } } diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java index d90962157..eaac5560a 100644 --- a/service/java/com/android/server/wifi/WifiStateMachine.java +++ b/service/java/com/android/server/wifi/WifiStateMachine.java @@ -114,6 +114,7 @@ import com.android.server.wifi.hotspot2.PasspointManager; import com.android.server.wifi.hotspot2.Utils; import com.android.server.wifi.hotspot2.WnmData; import com.android.server.wifi.nano.WifiMetricsProto; +import com.android.server.wifi.nano.WifiMetricsProto.StaEvent; import com.android.server.wifi.p2p.WifiP2pServiceImpl; import com.android.server.wifi.util.NativeUtil; import com.android.server.wifi.util.TelephonyUtil; @@ -1072,6 +1073,21 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.WPS_SUCCESS_EVENT, getHandler()); mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.WPS_TIMEOUT_EVENT, getHandler()); + mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ASSOCIATION_REJECTION_EVENT, + mWifiMetrics.getHandler()); + mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT, + mWifiMetrics.getHandler()); + mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_CONNECTION_EVENT, + mWifiMetrics.getHandler()); + mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_DISCONNECTION_EVENT, + mWifiMetrics.getHandler()); + mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, + mWifiMetrics.getHandler()); + mWifiMonitor.registerHandler(mInterfaceName, CMD_ASSOCIATED_BSSID, + mWifiMetrics.getHandler()); + mWifiMonitor.registerHandler(mInterfaceName, CMD_TARGET_BSSID, + mWifiMetrics.getHandler()); + final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED); @@ -1103,12 +1119,14 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss @Override public void onProvisioningSuccess(LinkProperties newLp) { + mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL); sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp); sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL); } @Override public void onProvisioningFailure(LinkProperties newLp) { + mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST); sendMessage(CMD_IP_CONFIGURATION_LOST); } @@ -1119,6 +1137,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss @Override public void onReachabilityLost(String logMsg) { + mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_REACHABILITY_LOST); sendMessage(CMD_IP_REACHABILITY_LOST, logMsg); } @@ -2884,10 +2903,6 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss if (newRssi > 0) newRssi -= 256; mWifiInfo.setRssi(newRssi); /* - * Log the rssi poll value in metrics - */ - mWifiMetrics.incrementRssiPollRssiCount(newRssi); - /* * Rather then sending the raw RSSI out every time it * changes, we precalculate the signal level that would * be displayed in the status bar, and only send the @@ -2921,6 +2936,12 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mWifiInfo.setFrequency(newFrequency); } mWifiConfigManager.updateScanDetailCacheFromWifiInfo(mWifiInfo); + /* + * Increment various performance metrics + */ + if (newRssi != null && newLinkSpeed != null && newFrequency != null) { + mWifiMetrics.handlePollResult(mWifiInfo); + } } // Polling has completed, hence we wont have a score anymore @@ -4818,6 +4839,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss break; case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST: if (message.arg1 == 1) { + mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT, + StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST); mWifiNative.disconnect(); mTemporarilyDisconnectWifi = true; } else { @@ -4913,6 +4936,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss WifiConfiguration.NetworkSelectionStatus .DISABLED_AUTHENTICATION_NO_CREDENTIALS); } + mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT, + StaEvent.DISCONNECT_GENERIC); mWifiNative.disconnect(); } break; @@ -4972,6 +4997,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss reportConnectionAttemptStart(config, mTargetRoamBSSID, WifiMetricsProto.ConnectionEvent.ROAM_UNRELATED); if (mWifiNative.connectToNetwork(config)) { + mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT, config); lastConnectAttemptTimestamp = mClock.getWallClockMillis(); targetWificonfiguration = config; mIsAutoRoaming = false; @@ -5039,6 +5065,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss WifiManager.NOT_AUTHORIZED); break; } + mWifiMetrics.logStaEvent(StaEvent.TYPE_CONNECT_NETWORK, config); broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config); replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED); break; @@ -5332,6 +5359,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss log("WifiNetworkAgent -> Wifi networkStatus valid, score= " + Integer.toString(mWifiInfo.score)); } + mWifiMetrics.logStaEvent(StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK); doNetworkStatus(status); } } @@ -5594,11 +5622,15 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss } break; case CMD_DISCONNECT: + mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT, + StaEvent.DISCONNECT_UNKNOWN); mWifiNative.disconnect(); transitionTo(mDisconnectingState); break; case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST: if (message.arg1 == 1) { + mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT, + StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST); mWifiNative.disconnect(); mTemporarilyDisconnectWifi = true; transitionTo(mDisconnectingState); @@ -5731,6 +5763,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(mLastNetworkId); if (TelephonyUtil.isSimConfig(config)) { + mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT, + StaEvent.DISCONNECT_RESET_SIM_NETWORKS); mWifiNative.disconnect(); transitionTo(mDisconnectingState); } @@ -5946,6 +5980,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss WifiMetricsProto.ConnectionEvent.HLF_NONE); mRoamFailCount++; handleNetworkDisconnect(); + mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT, + StaEvent.DISCONNECT_ROAM_WATCHDOG_TIMER); mWifiNative.disconnect(); transitionTo(mDisconnectedState); } @@ -6060,6 +6096,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss switch (message.what) { case CMD_UNWANTED_NETWORK: if (message.arg1 == NETWORK_STATUS_UNWANTED_DISCONNECT) { + mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT, + StaEvent.DISCONNECT_UNWANTED); mWifiNative.disconnect(); transitionTo(mDisconnectingState); } else if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN || @@ -6202,6 +6240,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss lastConnectAttemptTimestamp = mClock.getWallClockMillis(); targetWificonfiguration = config; mIsAutoRoaming = true; + mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_ROAM, config); transitionTo(mRoamingState); } else { loge("CMD_START_ROAM Failed to start roaming to network " + config); @@ -6315,6 +6354,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss class DisconnectedState extends State { @Override public void enter() { + Log.i(TAG, "disconnectedstate enter"); // We dont scan frequently if this is a temporary disconnect // due to p2p if (mTemporarilyDisconnectWifi) { @@ -6387,6 +6427,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss } break; case CMD_DISCONNECT: + mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT, + StaEvent.DISCONNECT_UNKNOWN); mWifiNative.disconnect(); break; /* Ignore network disconnect */ diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java index 38d2062e0..22744b63b 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java @@ -21,13 +21,19 @@ import static org.mockito.Mockito.*; import android.net.NetworkAgent; import android.net.wifi.ScanResult; +import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; +import android.net.wifi.WifiSsid; +import android.os.Handler; +import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; import android.util.Base64; + import com.android.server.wifi.hotspot2.NetworkDetail; import com.android.server.wifi.nano.WifiMetricsProto; +import com.android.server.wifi.nano.WifiMetricsProto.StaEvent; import org.junit.Before; import org.junit.Test; @@ -37,6 +43,7 @@ import org.mockito.MockitoAnnotations; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.BitSet; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -49,6 +56,7 @@ public class WifiMetricsTest { WifiMetrics mWifiMetrics; WifiMetricsProto.WifiLog mDeserializedWifiMetrics; + TestLooper mTestLooper; @Mock Clock mClock; @Before @@ -56,7 +64,8 @@ public class WifiMetricsTest { MockitoAnnotations.initMocks(this); mDeserializedWifiMetrics = null; when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 0); - mWifiMetrics = new WifiMetrics(mClock); + mTestLooper = new TestLooper(); + mWifiMetrics = new WifiMetrics(mClock, mTestLooper.getLooper()); } /** @@ -847,6 +856,156 @@ public class WifiMetricsTest { assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length); } + private static final int DEAUTH_REASON = 7; + private static final int ASSOC_STATUS = 11; + private static final int ASSOC_TIMEOUT = 1; + private static final int LOCAL_GEN = 1; + private static final int AUTH_FAILURE_REASON = WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD; + private static final int NUM_TEST_STA_EVENTS = 14; + private static final String sSSID = "\"SomeTestSsid\""; + private static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID); + private static final String sBSSID = "01:02:03:04:05:06"; + + private final StateChangeResult mStateDisconnected = + new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.DISCONNECTED); + private final StateChangeResult mStateCompleted = + new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED); + // Test bitmasks of supplicant state changes + private final int mSupBm1 = WifiMetrics.supplicantStateToBit(mStateDisconnected.state); + private final int mSupBm2 = WifiMetrics.supplicantStateToBit(mStateDisconnected.state) + | WifiMetrics.supplicantStateToBit(mStateCompleted.state); + // An invalid but interesting wifiConfiguration that exercises the StaEvent.ConfigInfo encoding + private final WifiConfiguration mTestWifiConfig = createComplexWifiConfig(); + // <msg.what> <msg.arg1> <msg.arg2> + private int[][] mTestStaMessageInts = { + {WifiMonitor.ASSOCIATION_REJECTION_EVENT, ASSOC_TIMEOUT, ASSOC_STATUS}, + {WifiMonitor.AUTHENTICATION_FAILURE_EVENT, 0, AUTH_FAILURE_REASON}, + {WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0}, + {WifiMonitor.NETWORK_DISCONNECTION_EVENT, LOCAL_GEN, DEAUTH_REASON}, + {WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0}, + {WifiStateMachine.CMD_ASSOCIATED_BSSID, 0, 0}, + {WifiStateMachine.CMD_TARGET_BSSID, 0, 0}, + {WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0}, + {WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0} + }; + private Object[] mTestStaMessageObjs = { + null, + null, + null, + null, + mStateDisconnected, + null, + null, + mStateDisconnected, + mStateCompleted + }; + // Values used to generate the StaEvent log calls from WifiStateMachine + // <StaEvent.Type>, <StaEvent.FrameworkDisconnectReason>, <1|0>(testWifiConfiguration, null) + private int[][] mTestStaLogInts = { + {StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL, 0, 0}, + {StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST, 0, 0}, + {StaEvent.TYPE_CMD_IP_REACHABILITY_LOST, 0, 0}, + {StaEvent.TYPE_CMD_START_CONNECT, 0, 1}, + {StaEvent.TYPE_CMD_START_ROAM, 0, 1}, + {StaEvent.TYPE_CONNECT_NETWORK, 0, 1}, + {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK, 0, 0}, + {StaEvent.TYPE_FRAMEWORK_DISCONNECT, StaEvent.DISCONNECT_API, 0} + }; + // Values used to generate the StaEvent log calls from WifiMonitor + // <type>, <reason>, <status>, <local_gen>, + // <auth_fail_reason>, <assoc_timed_out> <supplicantStateChangeBitmask> <1|0>(has ConfigInfo) + private int[][] mExpectedValues = { + {StaEvent.TYPE_ASSOCIATION_REJECTION_EVENT, -1, ASSOC_STATUS, 0, + /**/ 0, ASSOC_TIMEOUT, 0, 0}, /**/ + {StaEvent.TYPE_AUTHENTICATION_FAILURE_EVENT, -1, -1, 0, + /**/StaEvent.AUTH_FAILURE_WRONG_PSWD, 0, 0, 0}, /**/ + {StaEvent.TYPE_NETWORK_CONNECTION_EVENT, -1, -1, 0, + /**/ 0, 0, 0, 0}, /**/ + {StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT, DEAUTH_REASON, -1, LOCAL_GEN, + /**/ 0, 0, 0, 0}, /**/ + {StaEvent.TYPE_CMD_ASSOCIATED_BSSID, -1, -1, 0, + /**/ 0, 0, mSupBm1, 0}, /**/ + {StaEvent.TYPE_CMD_TARGET_BSSID, -1, -1, 0, + /**/ 0, 0, 0, 0}, /**/ + {StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL, -1, -1, 0, + /**/ 0, 0, mSupBm2, 0}, /**/ + {StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST, -1, -1, 0, + /**/ 0, 0, 0, 0}, /**/ + {StaEvent.TYPE_CMD_IP_REACHABILITY_LOST, -1, -1, 0, + /**/ 0, 0, 0, 0}, /**/ + {StaEvent.TYPE_CMD_START_CONNECT, -1, -1, 0, + /**/ 0, 0, 0, 1}, /**/ + {StaEvent.TYPE_CMD_START_ROAM, -1, -1, 0, + /**/ 0, 0, 0, 1}, /**/ + {StaEvent.TYPE_CONNECT_NETWORK, -1, -1, 0, + /**/ 0, 0, 0, 1}, /**/ + {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK, -1, -1, 0, + /**/ 0, 0, 0, 0}, /**/ + {StaEvent.TYPE_FRAMEWORK_DISCONNECT, -1, -1, 0, + /**/ 0, 0, 0, 0} /**/ + }; + + /** + * Generates events from all the rows in mTestStaMessageInts, and then mTestStaLogInts + */ + private void generateStaEvents(WifiMetrics wifiMetrics) { + Handler handler = wifiMetrics.getHandler(); + for (int i = 0; i < mTestStaMessageInts.length; i++) { + int[] mia = mTestStaMessageInts[i]; + handler.sendMessage( + handler.obtainMessage(mia[0], mia[1], mia[2], mTestStaMessageObjs[i])); + } + mTestLooper.dispatchAll(); + for (int i = 0; i < mTestStaLogInts.length; i++) { + int[] lia = mTestStaLogInts[i]; + wifiMetrics.logStaEvent(lia[0], lia[1], lia[2] == 1 ? mTestWifiConfig : null); + } + } + private void verifyDeserializedStaEvents(WifiMetricsProto.WifiLog wifiLog) { + assertEquals(NUM_TEST_STA_EVENTS, wifiLog.staEventList.length); + int j = 0; // De-serialized event index + for (int i = 0; i < mTestStaMessageInts.length; i++) { + StaEvent event = wifiLog.staEventList[j]; + int[] mia = mTestStaMessageInts[i]; + int[] evs = mExpectedValues[j]; + if (mia[0] != WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT) { + assertEquals(evs[0], event.type); + assertEquals(evs[1], event.reason); + assertEquals(evs[2], event.status); + assertEquals(evs[3] == 1 ? true : false, event.localGen); + assertEquals(evs[4], event.authFailureReason); + assertEquals(evs[5] == 1 ? true : false, event.associationTimedOut); + assertEquals(evs[6], event.supplicantStateChangesBitmask); + assertConfigInfoEqualsWifiConfig( + evs[7] == 1 ? mTestWifiConfig : null, event.configInfo); + j++; + } + } + } + + /** + * Generate StaEvents of each type, ensure all the different values are logged correctly, + * and that they survive serialization & de-serialization + */ + @Test + public void testStaEventsLogSerializeDeserialize() throws Exception { + generateStaEvents(mWifiMetrics); + dumpProtoAndDeserialize(); + verifyDeserializedStaEvents(mDeserializedWifiMetrics); + } + + /** + * Ensure the number of StaEvents does not exceed MAX_STA_EVENTS by generating lots of events + * and checking how many are deserialized + */ + @Test + public void testStaEventBounding() throws Exception { + for (int i = 0; i < (WifiMetrics.MAX_STA_EVENTS + 10); i++) { + mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT); + } + dumpProtoAndDeserialize(); + assertEquals(WifiMetrics.MAX_STA_EVENTS, mDeserializedWifiMetrics.staEventList.length); + } /** * Generate an RSSI delta event by creating a connection event and an RSSI poll within * 'interArrivalTime' milliseconds of each other. @@ -911,4 +1070,56 @@ public class WifiMetricsTest { writer.flush(); return stream.toString(); } + + private static final int TEST_ALLOWED_KEY_MANAGEMENT = 83; + private static final int TEST_ALLOWED_PROTOCOLS = 22; + private static final int TEST_ALLOWED_AUTH_ALGORITHMS = 11; + private static final int TEST_ALLOWED_PAIRWISE_CIPHERS = 67; + private static final int TEST_ALLOWED_GROUP_CIPHERS = 231; + private static final int TEST_CANDIDATE_LEVEL = -80; + private static final int TEST_CANDIDATE_FREQ = 2345; + + private WifiConfiguration createComplexWifiConfig() { + WifiConfiguration config = new WifiConfiguration(); + config.allowedKeyManagement = intToBitSet(TEST_ALLOWED_KEY_MANAGEMENT); + config.allowedProtocols = intToBitSet(TEST_ALLOWED_PROTOCOLS); + config.allowedAuthAlgorithms = intToBitSet(TEST_ALLOWED_AUTH_ALGORITHMS); + config.allowedPairwiseCiphers = intToBitSet(TEST_ALLOWED_PAIRWISE_CIPHERS); + config.allowedGroupCiphers = intToBitSet(TEST_ALLOWED_GROUP_CIPHERS); + config.hiddenSSID = true; + config.ephemeral = true; + config.getNetworkSelectionStatus().setHasEverConnected(true); + ScanResult candidate = new ScanResult(); + candidate.level = TEST_CANDIDATE_LEVEL; + candidate.frequency = TEST_CANDIDATE_FREQ; + config.getNetworkSelectionStatus().setCandidate(candidate); + return config; + } + + private void assertConfigInfoEqualsWifiConfig(WifiConfiguration config, + StaEvent.ConfigInfo info) { + if (config == null && info == null) return; + assertEquals(config.allowedKeyManagement, intToBitSet(info.allowedKeyManagement)); + assertEquals(config.allowedProtocols, intToBitSet(info.allowedProtocols)); + assertEquals(config.allowedAuthAlgorithms, intToBitSet(info.allowedAuthAlgorithms)); + assertEquals(config.allowedPairwiseCiphers, intToBitSet(info.allowedPairwiseCiphers)); + assertEquals(config.allowedGroupCiphers, intToBitSet(info.allowedGroupCiphers)); + assertEquals(config.hiddenSSID, info.hiddenSsid); + assertEquals(config.ephemeral, info.isEphemeral); + assertEquals(config.getNetworkSelectionStatus().getHasEverConnected(), + info.hasEverConnected); + assertEquals(config.getNetworkSelectionStatus().getCandidate().level, info.scanRssi); + assertEquals(config.getNetworkSelectionStatus().getCandidate().frequency, info.scanFreq); + } + + /** + * Sets the values of bitSet to match an int mask + */ + private static BitSet intToBitSet(int mask) { + BitSet bitSet = new BitSet(); + for (int bitIndex = 0; mask > 0; mask >>>= 1, bitIndex++) { + if ((mask & 1) != 0) bitSet.set(bitIndex); + } + return bitSet; + } } diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java index 3cd08439e..e7c5fa962 100644 --- a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java +++ b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java @@ -105,7 +105,6 @@ public class WifiScanningServiceTest { mAlarmManager = new TestAlarmManager(); when(mContext.getSystemService(Context.ALARM_SERVICE)) .thenReturn(mAlarmManager.getAlarmManager()); - mWifiMetrics = new WifiMetrics(mClock); ChannelHelper channelHelper = new PresetKnownBandsChannelHelper( new int[]{2400, 2450}, @@ -113,6 +112,7 @@ public class WifiScanningServiceTest { new int[]{5600, 5650, 5660}); mLooper = new TestLooper(); + mWifiMetrics = new WifiMetrics(mClock, mLooper.getLooper()); when(mWifiScannerImplFactory .create(any(), any(), any())) .thenReturn(mWifiScannerImpl); |