WifiMetrics.java revision 4dead162c5336443e9d7b3deae5eb26b07d39254
1/*
2 * Copyright (C) 2016 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
17package com.android.server.wifi;
18
19import android.net.wifi.ScanResult;
20import android.net.wifi.WifiConfiguration;
21import android.os.SystemClock;
22import android.util.Base64;
23import android.util.SparseArray;
24
25import java.io.FileDescriptor;
26import java.io.PrintWriter;
27import java.util.ArrayList;
28import java.util.Calendar;
29import java.util.List;
30
31/**
32 * Provides storage for wireless connectivity metrics, as they are generated.
33 * Metrics logged by this class include:
34 *   Aggregated connection stats (num of connections, num of failures, ...)
35 *   Discrete connection event stats (time, duration, failure codes, ...)
36 *   Router details (technology type, authentication type, ...)
37 *   Scan stats
38 */
39public class WifiMetrics {
40    private static final String TAG = "WifiMetrics";
41    private final Object mLock = new Object();
42    private static final int MAX_CONNECTION_EVENTS = 256;
43    /**
44     * Metrics are stored within an instance of the WifiLog proto during runtime,
45     * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during
46     * runtime in member lists of this WifiMetrics class, with the final WifiLog proto being pieced
47     * together at dump-time
48     */
49    private final WifiMetricsProto.WifiLog mWifiLogProto;
50    /**
51     * Session information that gets logged for every Wifi connection attempt.
52     */
53    private final List<ConnectionEvent> mConnectionEventList;
54    /**
55     * The latest started (but un-ended) connection attempt
56     */
57    private ConnectionEvent mCurrentConnectionEvent;
58    /**
59     * Count of number of times each scan return code, indexed by WifiLog.ScanReturnCode
60     */
61    private final SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry> mScanReturnEntries;
62    /**
63     * Mapping of system state to the counts of scans requested in that wifi state * screenOn
64     * combination. Indexed by WifiLog.WifiState * (1 + screenOn)
65     */
66    private final SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry>
67            mWifiSystemStateEntries;
68
69    class RouterFingerPrint {
70        private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
71        RouterFingerPrint() {
72            mRouterFingerPrintProto = new WifiMetricsProto.RouterFingerPrint();
73        }
74
75        public String toString() {
76            StringBuilder sb = new StringBuilder();
77            synchronized (mLock) {
78                sb.append("mConnectionEvent.roamType=" + mRouterFingerPrintProto.roamType);
79                sb.append(", mChannelInfo=" + mRouterFingerPrintProto.channelInfo);
80                sb.append(", mDtim=" + mRouterFingerPrintProto.dtim);
81                sb.append(", mAuthentication=" + mRouterFingerPrintProto.authentication);
82                sb.append(", mHidden=" + mRouterFingerPrintProto.hidden);
83                sb.append(", mRouterTechnology=" + mRouterFingerPrintProto.routerTechnology);
84                sb.append(", mSupportsIpv6=" + mRouterFingerPrintProto.supportsIpv6);
85            }
86            return sb.toString();
87        }
88        public void updateFromWifiConfiguration(WifiConfiguration config) {
89            if (config != null) {
90                /*<TODO>
91                mRouterFingerPrintProto.roamType
92                mRouterFingerPrintProto.routerTechnology
93                mRouterFingerPrintProto.supportsIpv6
94                */
95                if (config.allowedAuthAlgorithms != null
96                        && config.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.OPEN)) {
97                    mRouterFingerPrintProto.authentication =
98                            WifiMetricsProto.RouterFingerPrint.AUTH_OPEN;
99                } else if (config.isEnterprise()) {
100                    mRouterFingerPrintProto.authentication =
101                            WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE;
102                } else {
103                    mRouterFingerPrintProto.authentication =
104                            WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
105                }
106                mRouterFingerPrintProto.hidden = config.hiddenSSID;
107                mRouterFingerPrintProto.channelInfo = config.apChannel;
108                // Config may not have a valid dtimInterval set yet, in which case dtim will be zero
109                // (These are only populated from beacon frame scan results, which are returned as
110                // scan results from the chip far less frequently than Probe-responses)
111                if (config.dtimInterval > 0) {
112                    mRouterFingerPrintProto.dtim = config.dtimInterval;
113                }
114            }
115        }
116    }
117
118    /**
119     * Log event, tracking the start time, end time and result of a wireless connection attempt.
120     */
121    class ConnectionEvent {
122        WifiMetricsProto.ConnectionEvent mConnectionEvent;
123        //<TODO> Move these constants into a wifi.proto Enum
124        // Level 2 Failure Codes
125        // Failure is unknown
126        public static final int LLF_UNKNOWN = 0;
127        // NONE
128        public static final int LLF_NONE = 1;
129        // ASSOCIATION_REJECTION_EVENT
130        public static final int LLF_ASSOCIATION_REJECTION = 2;
131        // AUTHENTICATION_FAILURE_EVENT
132        public static final int LLF_AUTHENTICATION_FAILURE = 3;
133        // SSID_TEMP_DISABLED (Also Auth failure)
134        public static final int LLF_SSID_TEMP_DISABLED = 4;
135        // reconnect() or reassociate() call to WifiNative failed
136        public static final int LLF_CONNECT_NETWORK_FAILED = 5;
137        // NETWORK_DISCONNECTION_EVENT
138        public static final int LLF_NETWORK_DISCONNECTION = 6;
139        // NEW_CONNECTION_ATTEMPT before previous finished
140        public static final int LLF_NEW_CONNECTION_ATTEMPT = 7;
141        RouterFingerPrint mRouterFingerPrint;
142        private long mRealStartTime;
143        private long mRealEndTime;
144        private String mConfigSsid;
145        private String mConfigBssid;
146
147        private ConnectionEvent() {
148            mConnectionEvent = new WifiMetricsProto.ConnectionEvent();
149            mRealEndTime = 0;
150            mRealStartTime = 0;
151            mRouterFingerPrint = new RouterFingerPrint();
152            mConnectionEvent.routerFingerprint = mRouterFingerPrint.mRouterFingerPrintProto;
153            mConfigSsid = "<NULL>";
154            mConfigBssid = "<NULL>";
155        }
156
157        public String toString() {
158            StringBuilder sb = new StringBuilder();
159            sb.append("startTime=");
160            Calendar c = Calendar.getInstance();
161            synchronized (mLock) {
162                c.setTimeInMillis(mConnectionEvent.startTimeMillis);
163                sb.append(mConnectionEvent.startTimeMillis == 0 ? "            <null>" :
164                        String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
165                sb.append(", SSID=");
166                sb.append(mConfigSsid);
167                sb.append(", BSSID=");
168                sb.append(mConfigBssid);
169                sb.append(", durationMillis=");
170                sb.append(mConnectionEvent.durationTakenToConnectMillis);
171                sb.append(", roamType=");
172                switch(mConnectionEvent.roamType) {
173                    case 1:
174                        sb.append("ROAM_NONE");
175                        break;
176                    case 2:
177                        sb.append("ROAM_DBDC");
178                        break;
179                    case 3:
180                        sb.append("ROAM_ENTERPRISE");
181                        break;
182                    case 4:
183                        sb.append("ROAM_USER_SELECTED");
184                        break;
185                    case 5:
186                        sb.append("ROAM_UNRELATED");
187                        break;
188                    default:
189                        sb.append("ROAM_UNKNOWN");
190                }
191                sb.append(", connectionResult=");
192                sb.append(mConnectionEvent.connectionResult);
193                sb.append(", level2FailureCode=");
194                switch(mConnectionEvent.level2FailureCode) {
195                    case LLF_NONE:
196                        sb.append("NONE");
197                        break;
198                    case LLF_ASSOCIATION_REJECTION:
199                        sb.append("ASSOCIATION_REJECTION");
200                        break;
201                    case LLF_AUTHENTICATION_FAILURE:
202                        sb.append("AUTHENTICATION_FAILURE");
203                        break;
204                    case LLF_SSID_TEMP_DISABLED:
205                        sb.append("SSID_TEMP_DISABLED");
206                        break;
207                    case LLF_CONNECT_NETWORK_FAILED:
208                        sb.append("CONNECT_NETWORK_FAILED");
209                        break;
210                    case LLF_NETWORK_DISCONNECTION:
211                        sb.append("NETWORK_DISCONNECTION");
212                        break;
213                    case LLF_NEW_CONNECTION_ATTEMPT:
214                        sb.append("NEW_CONNECTION_ATTEMPT");
215                        break;
216                    default:
217                        sb.append("UNKNOWN");
218                        break;
219                }
220                sb.append(", connectivityLevelFailureCode=");
221                switch(mConnectionEvent.connectivityLevelFailureCode) {
222                    case WifiMetricsProto.ConnectionEvent.HLF_NONE:
223                        sb.append("NONE");
224                        break;
225                    case WifiMetricsProto.ConnectionEvent.HLF_DHCP:
226                        sb.append("DHCP");
227                        break;
228                    case WifiMetricsProto.ConnectionEvent.HLF_NO_INTERNET:
229                        sb.append("NO_INTERNET");
230                        break;
231                    case WifiMetricsProto.ConnectionEvent.HLF_UNWANTED:
232                        sb.append("UNWANTED");
233                        break;
234                    default:
235                        sb.append("UNKNOWN");
236                        break;
237                }
238                sb.append(", signalStrength=");
239                sb.append(mConnectionEvent.signalStrength);
240                sb.append("\n  ");
241                sb.append("mRouterFingerprint: ");
242                sb.append(mRouterFingerPrint.toString());
243            }
244            return sb.toString();
245        }
246    }
247
248    public WifiMetrics() {
249        mWifiLogProto = new WifiMetricsProto.WifiLog();
250        mConnectionEventList = new ArrayList<>();
251        mCurrentConnectionEvent = null;
252        mScanReturnEntries = new SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry>();
253        mWifiSystemStateEntries = new SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry>();
254    }
255
256    /**
257     * Create a new connection event. Call when wifi attempts to make a new network connection
258     * If there is a current 'un-ended' connection event, it will be ended with UNKNOWN connectivity
259     * failure code.
260     * Gathers and sets the RouterFingerPrint data as well
261     *
262     * @param config WifiConfiguration of the config used for the current connection attempt
263     * @param roamType Roam type that caused connection attempt, see WifiMetricsProto.WifiLog.ROAM_X
264     */
265    public void startConnectionEvent(WifiConfiguration config, int roamType) {
266        if (mCurrentConnectionEvent != null) {
267            endConnectionEvent(ConnectionEvent.LLF_NEW_CONNECTION_ATTEMPT,
268                    WifiMetricsProto.ConnectionEvent.HLF_NONE);
269        }
270        synchronized (mLock) {
271            //If at maximum connection events, start removing the oldest
272            while(mConnectionEventList.size() >= MAX_CONNECTION_EVENTS) {
273                mConnectionEventList.remove(0);
274            }
275            mCurrentConnectionEvent = new ConnectionEvent();
276            mCurrentConnectionEvent.mConnectionEvent.startTimeMillis =
277                    System.currentTimeMillis();
278            mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
279            mCurrentConnectionEvent.mRouterFingerPrint.updateFromWifiConfiguration(config);
280            if (config != null) {
281                //RSSI
282                ScanResult candidate = config.getNetworkSelectionStatus().getCandidate();
283                if (candidate != null) {
284                    mCurrentConnectionEvent.mConnectionEvent.signalStrength =
285                            candidate.level;
286                }
287                mCurrentConnectionEvent.mConfigSsid = config.SSID;
288                mCurrentConnectionEvent.mConfigBssid = config.BSSID;
289            }
290            mCurrentConnectionEvent.mRealStartTime = SystemClock.elapsedRealtime();
291            mConnectionEventList.add(mCurrentConnectionEvent);
292        }
293    }
294
295    /**
296     * set the RoamType of the current ConnectionEvent (if any)
297     */
298    public void setConnectionEventRoamType(int roamType) {
299        if (mCurrentConnectionEvent != null) {
300            mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
301        }
302    }
303    /**
304     * End a Connection event record. Call when wifi connection attempt succeeds or fails.
305     * If a Connection event has not been started and is active when .end is called, a new one is
306     * created with zero duration.
307     *
308     * @param level2FailureCode Level 2 failure code returned by supplicant
309     * @param connectivityFailureCode WifiMetricsProto.ConnectionEvent.HLF_X
310     */
311    public void endConnectionEvent(int level2FailureCode, int connectivityFailureCode) {
312        synchronized (mLock) {
313            if (mCurrentConnectionEvent != null) {
314                boolean result = (level2FailureCode == 1)
315                        && (connectivityFailureCode == WifiMetricsProto.ConnectionEvent.HLF_NONE);
316                mCurrentConnectionEvent.mConnectionEvent.connectionResult = result ? 1 : 0;
317                mCurrentConnectionEvent.mRealEndTime = SystemClock.elapsedRealtime();
318                mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int)
319                        (mCurrentConnectionEvent.mRealEndTime
320                        - mCurrentConnectionEvent.mRealStartTime);
321                mCurrentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode;
322                mCurrentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode =
323                        connectivityFailureCode;
324                //ConnectionEvent already added to ConnectionEvents List
325                mCurrentConnectionEvent = null;
326            }
327        }
328    }
329
330    void setNumSavedNetworks(int num) {
331        synchronized (mLock) {
332            mWifiLogProto.numSavedNetworks = num;
333        }
334    }
335
336    void setNumOpenNetworks(int num) {
337        synchronized (mLock) {
338            mWifiLogProto.numOpenNetworks = num;
339        }
340    }
341
342    void setNumPersonalNetworks(int num) {
343        synchronized (mLock) {
344            mWifiLogProto.numPersonalNetworks = num;
345        }
346    }
347
348    void setNumEnterpriseNetworks(int num) {
349        synchronized (mLock) {
350            mWifiLogProto.numEnterpriseNetworks = num;
351        }
352    }
353
354    void setNumNetworksAddedByUser(int num) {
355        synchronized (mLock) {
356            mWifiLogProto.numNetworksAddedByUser = num;
357        }
358    }
359
360    void setNumNetworksAddedByApps(int num) {
361        synchronized (mLock) {
362            mWifiLogProto.numNetworksAddedByApps = num;
363        }
364    }
365
366    void setIsLocationEnabled(boolean enabled) {
367        synchronized (mLock) {
368            mWifiLogProto.isLocationEnabled = enabled;
369        }
370    }
371
372    void setIsScanningAlwaysEnabled(boolean enabled) {
373        synchronized (mLock) {
374            mWifiLogProto.isScanningAlwaysEnabled = enabled;
375        }
376    }
377
378    /**
379     * Increment Airplane mode toggle count
380     */
381    public void incrementAirplaneToggleCount() {
382        synchronized (mLock) {
383            mWifiLogProto.numWifiToggledViaAirplane++;
384        }
385    }
386
387    /**
388     * Increment Wifi Toggle count
389     */
390    public void incrementWifiToggleCount() {
391        synchronized (mLock) {
392            mWifiLogProto.numWifiToggledViaSettings++;
393        }
394    }
395
396    /**
397     * Increment Non Empty Scan Results count
398     */
399    public void incrementNonEmptyScanResultCount() {
400        synchronized (mLock) {
401            mWifiLogProto.numNonEmptyScanResults++;
402        }
403    }
404
405    /**
406     * Increment Empty Scan Results count
407     */
408    public void incrementEmptyScanResultCount() {
409        synchronized (mLock) {
410            mWifiLogProto.numEmptyScanResults++;
411        }
412    }
413
414    /**
415     * Increment count of scan return code occurrence
416     *
417     * @param scanReturnCode Return code from scan attempt WifiMetricsProto.WifiLog.SCAN_X
418     */
419    public void incrementScanReturnEntry(int scanReturnCode) {
420        synchronized (mLock) {
421            WifiMetricsProto.WifiLog.ScanReturnEntry entry = mScanReturnEntries.get(scanReturnCode);
422            if (entry == null) {
423                entry = new WifiMetricsProto.WifiLog.ScanReturnEntry();
424                entry.scanReturnCode = scanReturnCode;
425                entry.scanResultsCount = 0;
426            }
427            entry.scanResultsCount++;
428            mScanReturnEntries.put(scanReturnCode, entry);
429        }
430    }
431
432    /**
433     * Increments the count of scans initiated by each wifi state, accounts for screenOn/Off
434     *
435     * @param state State of the system when scan was initiated, see WifiMetricsProto.WifiLog.WIFI_X
436     * @param screenOn Is the screen on
437     */
438    public void incrementWifiSystemScanStateCount(int state, boolean screenOn) {
439        synchronized (mLock) {
440            int index = state * (screenOn ? 2 : 1);
441            WifiMetricsProto.WifiLog.WifiSystemStateEntry entry =
442                    mWifiSystemStateEntries.get(index);
443            if (entry == null) {
444                entry = new WifiMetricsProto.WifiLog.WifiSystemStateEntry();
445                entry.wifiState = state;
446                entry.wifiStateCount = 0;
447                entry.isScreenOn = screenOn;
448            }
449            entry.wifiStateCount++;
450            mWifiSystemStateEntries.put(state, entry);
451        }
452    }
453
454    public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
455    /**
456     * Dump all WifiMetrics. Collects some metrics from ConfigStore, Settings and WifiManager
457     * at this time
458     *
459     * @param fd unused
460     * @param pw PrintWriter for writing dump to
461     * @param args unused
462     */
463    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
464        synchronized (mLock) {
465            pw.println("WifiMetrics:");
466            if (args.length > 0 && PROTO_DUMP_ARG.equals(args[0])) {
467                //Dump serialized WifiLog proto
468                consolidateProto(true);
469                for (ConnectionEvent event : mConnectionEventList) {
470                    if (mCurrentConnectionEvent != event) {
471                        //indicate that automatic bug report has been taken for all valid
472                        //connection events
473                        event.mConnectionEvent.automaticBugReportTaken = true;
474                    }
475                }
476                byte[] wifiMetricsProto = WifiMetricsProto.WifiLog.toByteArray(mWifiLogProto);
477                String metricsProtoDump = Base64.encodeToString(wifiMetricsProto, Base64.DEFAULT);
478                pw.println(metricsProtoDump);
479                pw.println("EndWifiMetrics");
480                clear();
481            } else {
482                pw.println("mConnectionEvents:");
483                for (ConnectionEvent event : mConnectionEventList) {
484                    String eventLine = event.toString();
485                    if (event == mCurrentConnectionEvent) {
486                        eventLine += "CURRENTLY OPEN EVENT";
487                    }
488                    pw.println(eventLine);
489                }
490                pw.println("mWifiLogProto.numSavedNetworks=" + mWifiLogProto.numSavedNetworks);
491                pw.println("mWifiLogProto.numOpenNetworks=" + mWifiLogProto.numOpenNetworks);
492                pw.println("mWifiLogProto.numPersonalNetworks="
493                        + mWifiLogProto.numPersonalNetworks);
494                pw.println("mWifiLogProto.numEnterpriseNetworks="
495                        + mWifiLogProto.numEnterpriseNetworks);
496                pw.println("mWifiLogProto.isLocationEnabled=" + mWifiLogProto.isLocationEnabled);
497                pw.println("mWifiLogProto.isScanningAlwaysEnabled="
498                        + mWifiLogProto.isScanningAlwaysEnabled);
499                pw.println("mWifiLogProto.numWifiToggledViaSettings="
500                        + mWifiLogProto.numWifiToggledViaSettings);
501                pw.println("mWifiLogProto.numWifiToggledViaAirplane="
502                        + mWifiLogProto.numWifiToggledViaAirplane);
503                pw.println("mWifiLogProto.numNetworksAddedByUser="
504                        + mWifiLogProto.numNetworksAddedByUser);
505                //TODO - Pending scanning refactor
506                pw.println("mWifiLogProto.numNetworksAddedByApps=" + "<TODO>");
507                pw.println("mWifiLogProto.numNonEmptyScanResults=" + "<TODO>");
508                pw.println("mWifiLogProto.numEmptyScanResults=" + "<TODO>");
509                pw.println("mWifiLogProto.numOneshotScans=" + "<TODO>");
510                pw.println("mWifiLogProto.numBackgroundScans=" + "<TODO>");
511                pw.println("mScanReturnEntries:" + " <TODO>");
512                pw.println("mSystemStateEntries:" + " <TODO>");
513            }
514        }
515    }
516
517    /**
518     * Assign the separate ConnectionEvent, SystemStateEntry and ScanReturnCode lists to their
519     * respective lists within mWifiLogProto, and clear the original lists managed here.
520     *
521     * @param incremental Only include ConnectionEvents created since last automatic bug report
522     */
523    private void consolidateProto(boolean incremental) {
524        List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>();
525        synchronized (mLock) {
526            for (ConnectionEvent event : mConnectionEventList) {
527                if (!incremental || ((mCurrentConnectionEvent != event)
528                        && !event.mConnectionEvent.automaticBugReportTaken)) {
529                    //Get all ConnectionEvents that haven not been dumped as a proto, also exclude
530                    //the current active un-ended connection event
531                    events.add(event.mConnectionEvent);
532                    event.mConnectionEvent.automaticBugReportTaken = true;
533                }
534            }
535            if (events.size() > 0) {
536                mWifiLogProto.connectionEvent = events.toArray(mWifiLogProto.connectionEvent);
537            }
538            //<TODO> SystemStateEntry and ScanReturnCode list consolidation
539        }
540    }
541
542    /**
543     * Serializes all of WifiMetrics to WifiLog proto, and returns the byte array.
544     * Does not count as taking an automatic bug report
545     *
546     * @return byte array of the deserialized & consolidated Proto
547     */
548    public byte[] toByteArray() {
549        consolidateProto(false);
550        return mWifiLogProto.toByteArray(mWifiLogProto);
551    }
552
553    /**
554     * Clear all WifiMetrics, except for currentConnectionEvent.
555     */
556    private void clear() {
557        synchronized (mLock) {
558            mConnectionEventList.clear();
559            if (mCurrentConnectionEvent != null) {
560                mConnectionEventList.add(mCurrentConnectionEvent);
561            }
562            mScanReturnEntries.clear();
563            mWifiSystemStateEntries.clear();
564            mWifiLogProto.clear();
565        }
566    }
567}
568