WifiMetrics.java revision 2532a24b254d724a9b6771d327dc410b32b18602
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.WifiConfiguration;
20import android.net.wifi.WifiInfo;
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.dtim
93                mRouterFingerPrintProto.routerTechnology
94                mRouterFingerPrintProto.supportsIpv6
95                */
96                if (config.allowedAuthAlgorithms != null
97                        && config.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.OPEN)) {
98                    mRouterFingerPrintProto.authentication =
99                            WifiMetricsProto.RouterFingerPrint.AUTH_OPEN;
100                } else if (config.isEnterprise()) {
101                    mRouterFingerPrintProto.authentication =
102                            WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE;
103                } else {
104                    mRouterFingerPrintProto.authentication =
105                            WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
106                }
107                mRouterFingerPrintProto.hidden = config.hiddenSSID;
108                mRouterFingerPrintProto.channelInfo = config.apChannel;
109
110            }
111        }
112    }
113
114    /**
115     * Log event, tracking the start time, end time and result of a wireless connection attempt.
116     */
117    class ConnectionEvent {
118        WifiMetricsProto.ConnectionEvent mConnectionEvent;
119        RouterFingerPrint mRouterFingerPrint;
120        private long mRealStartTime;
121        /**
122         * Bitset tracking the capture completeness of this connection event bit 1='Event started',
123         * bit 2='Event ended' value = 3 for capture completeness
124         */
125        private int mEventCompleteness;
126        private long mRealEndTime;
127
128        //<TODO> Move these constants into a wifi.proto Enum
129        // Level 2 Failure Codes
130        // Failure is unknown
131        public static final int LLF_UNKNOWN = 0;
132        // NONE
133        public static final int LLF_NONE = 1;
134        // ASSOCIATION_REJECTION_EVENT
135        public static final int LLF_ASSOCIATION_REJECTION = 2;
136        // AUTHENTICATION_FAILURE_EVENT
137        public static final int LLF_AUTHENTICATION_FAILURE = 3;
138        // SSID_TEMP_DISABLED (Also Auth failure)
139        public static final int LLF_SSID_TEMP_DISABLED = 4;
140        // CONNECT_NETWORK_FAILED
141        public static final int LLF_CONNECT_NETWORK_FAILED = 5;
142        // NETWORK_DISCONNECTION_EVENT
143        public static final int LLF_NETWORK_DISCONNECTION = 6;
144
145        private ConnectionEvent() {
146            mConnectionEvent = new WifiMetricsProto.ConnectionEvent();
147            mConnectionEvent.startTimeMillis = -1;
148            mRealEndTime = -1;
149            mConnectionEvent.durationTakenToConnectMillis = -1;
150            mRouterFingerPrint = new RouterFingerPrint();
151            mConnectionEvent.routerFingerprint = mRouterFingerPrint.mRouterFingerPrintProto;
152            mConnectionEvent.signalStrength = -1;
153            mConnectionEvent.roamType = WifiMetricsProto.ConnectionEvent.ROAM_UNKNOWN;
154            mConnectionEvent.connectionResult = -1;
155            mConnectionEvent.level2FailureCode = -1;
156            mConnectionEvent.connectivityLevelFailureCode =
157                    WifiMetricsProto.ConnectionEvent.HLF_UNKNOWN;
158            mConnectionEvent.automaticBugReportTaken = false;
159            mEventCompleteness = 0;
160        }
161
162        public String toString() {
163            StringBuilder sb = new StringBuilder();
164            sb.append("startTime=");
165            Calendar c = Calendar.getInstance();
166            synchronized (mLock) {
167                c.setTimeInMillis(mConnectionEvent.startTimeMillis);
168                sb.append(mConnectionEvent.startTimeMillis == 0 ? "            <null>" :
169                        String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
170                sb.append(", endTime=");
171                c.setTimeInMillis(mRealEndTime);
172                sb.append(mRealEndTime == 0 ? "            <null>" :
173                        String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
174                sb.append(", durationTakenToConnectMillis=");
175                sb.append(mConnectionEvent.durationTakenToConnectMillis);
176                sb.append(", roamType=");
177                switch(mConnectionEvent.roamType){
178                    case 1:
179                        sb.append("ROAM_NONE");
180                        break;
181                    case 2:
182                        sb.append("ROAM_DBDC");
183                        break;
184                    case 3:
185                        sb.append("ROAM_ENTERPRISE");
186                        break;
187                    case 4:
188                        sb.append("ROAM_USER_SELECTED");
189                        break;
190                    case 5:
191                        sb.append("ROAM_UNRELATED");
192                        break;
193                    default:
194                        sb.append("ROAM_UNKNOWN");
195                }
196                sb.append(", level2FailureCode=");
197                sb.append(mConnectionEvent.level2FailureCode);
198                sb.append(", connectivityLevelFailureCode=");
199                sb.append(mConnectionEvent.connectivityLevelFailureCode);
200                sb.append(", mEventCompleteness=");
201                sb.append(mEventCompleteness);
202                sb.append("\n  ");
203                sb.append("mRouterFingerprint: ");
204                sb.append(mRouterFingerPrint.toString());
205            }
206            return sb.toString();
207        }
208    }
209
210    public WifiMetrics() {
211        mWifiLogProto = new WifiMetricsProto.WifiLog();
212        mConnectionEventList = new ArrayList<>();
213        mCurrentConnectionEvent = null;
214        mScanReturnEntries = new SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry>();
215        mWifiSystemStateEntries = new SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry>();
216    }
217
218    /**
219     * Create a new connection event. Call when wifi attempts to make a new network connection
220     * If there is a current 'un-ended' connection event, it will be ended with UNKNOWN connectivity
221     * failure code.
222     * Gathers and sets the RouterFingerPrint data as well
223     *
224     * @param wifiInfo WifiInfo for the current connection attempt, used for connection metrics
225     * @param roamType Roam type that caused connection attempt, see WifiMetricsProto.WifiLog.ROAM_X
226     */
227    public void startConnectionEvent(WifiInfo wifiInfo, WifiConfiguration config, int roamType) {
228        synchronized (mLock) {
229            if (mConnectionEventList.size() <= MAX_CONNECTION_EVENTS) {
230                mCurrentConnectionEvent = new ConnectionEvent();
231                mCurrentConnectionEvent.mEventCompleteness |= 1;
232                mCurrentConnectionEvent.mConnectionEvent.startTimeMillis =
233                        System.currentTimeMillis();
234                mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
235                mCurrentConnectionEvent.mRouterFingerPrint.updateFromWifiConfiguration(config);
236                if (wifiInfo != null) {
237                    mCurrentConnectionEvent.mConnectionEvent.signalStrength = wifiInfo.getRssi();
238                }
239                mCurrentConnectionEvent.mRealStartTime = SystemClock.elapsedRealtime();
240                mConnectionEventList.add(mCurrentConnectionEvent);
241            }
242        }
243    }
244
245    public void startConnectionEvent(WifiInfo wifiInfo) {
246        startConnectionEvent(wifiInfo, null, WifiMetricsProto.ConnectionEvent.ROAM_NONE);
247    }
248
249    /**
250     * set the RoamType of the current ConnectionEvent (if any)
251     */
252    public void setConnectionEventRoamType(int roamType) {
253        if (mCurrentConnectionEvent != null) {
254            mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
255        }
256    }
257    /**
258     * End a Connection event record. Call when wifi connection attempt succeeds or fails.
259     * If a Connection event has not been started and is active when .end is called, a new one is
260     * created with zero duration.
261     *
262     * @param level2FailureCode Level 2 failure code returned by supplicant
263     * @param connectivityFailureCode WifiMetricsProto.ConnectionEvent.HLF_X
264     */
265    public void endConnectionEvent(int level2FailureCode, int connectivityFailureCode) {
266        synchronized (mLock) {
267            if (mCurrentConnectionEvent != null) {
268                boolean result = (level2FailureCode == 1)
269                        && (connectivityFailureCode == WifiMetricsProto.ConnectionEvent.HLF_NONE);
270                mCurrentConnectionEvent.mConnectionEvent.connectionResult = result ? 1 : 0;
271                mCurrentConnectionEvent.mEventCompleteness |= 2;
272                mCurrentConnectionEvent.mRealEndTime = SystemClock.elapsedRealtime();
273                mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int)
274                        (mCurrentConnectionEvent.mRealEndTime
275                        - mCurrentConnectionEvent.mRealStartTime);
276                mCurrentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode;
277                mCurrentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode =
278                        connectivityFailureCode;
279                //ConnectionEvent already added to ConnectionEvents List
280                mCurrentConnectionEvent = null;
281            }
282        }
283    }
284
285    void setNumSavedNetworks(int num) {
286        synchronized (mLock) {
287            mWifiLogProto.numSavedNetworks = num;
288        }
289    }
290
291    void setNumOpenNetworks(int num) {
292        synchronized (mLock) {
293            mWifiLogProto.numOpenNetworks = num;
294        }
295    }
296
297    void setNumPersonalNetworks(int num) {
298        synchronized (mLock) {
299            mWifiLogProto.numPersonalNetworks = num;
300        }
301    }
302
303    void setNumEnterpriseNetworks(int num) {
304        synchronized (mLock) {
305            mWifiLogProto.numEnterpriseNetworks = num;
306        }
307    }
308
309    void setNumNetworksAddedByUser(int num) {
310        synchronized (mLock) {
311            mWifiLogProto.numNetworksAddedByUser = num;
312        }
313    }
314
315    void setNumNetworksAddedByApps(int num) {
316        synchronized (mLock) {
317            mWifiLogProto.numNetworksAddedByApps = num;
318        }
319    }
320
321    void setIsLocationEnabled(boolean enabled) {
322        synchronized (mLock) {
323            mWifiLogProto.isLocationEnabled = enabled;
324        }
325    }
326
327    void setIsScanningAlwaysEnabled(boolean enabled) {
328        synchronized (mLock) {
329            mWifiLogProto.isScanningAlwaysEnabled = enabled;
330        }
331    }
332
333    /**
334     * Increment Airplane mode toggle count
335     */
336    public void incrementAirplaneToggleCount() {
337        synchronized (mLock) {
338            mWifiLogProto.numWifiToggledViaAirplane++;
339        }
340    }
341
342    /**
343     * Increment Wifi Toggle count
344     */
345    public void incrementWifiToggleCount() {
346        synchronized (mLock) {
347            mWifiLogProto.numWifiToggledViaSettings++;
348        }
349    }
350
351    /**
352     * Increment Non Empty Scan Results count
353     */
354    public void incrementNonEmptyScanResultCount() {
355        synchronized (mLock) {
356            mWifiLogProto.numNonEmptyScanResults++;
357        }
358    }
359
360    /**
361     * Increment Empty Scan Results count
362     */
363    public void incrementEmptyScanResultCount() {
364        synchronized (mLock) {
365            mWifiLogProto.numEmptyScanResults++;
366        }
367    }
368
369    /**
370     * Increment count of scan return code occurrence
371     *
372     * @param scanReturnCode Return code from scan attempt WifiMetricsProto.WifiLog.SCAN_X
373     */
374    public void incrementScanReturnEntry(int scanReturnCode) {
375        synchronized (mLock) {
376            WifiMetricsProto.WifiLog.ScanReturnEntry entry = mScanReturnEntries.get(scanReturnCode);
377            if (entry == null) {
378                entry = new WifiMetricsProto.WifiLog.ScanReturnEntry();
379                entry.scanReturnCode = scanReturnCode;
380                entry.scanResultsCount = 0;
381            }
382            entry.scanResultsCount++;
383            mScanReturnEntries.put(scanReturnCode, entry);
384        }
385    }
386
387    /**
388     * Increments the count of scans initiated by each wifi state, accounts for screenOn/Off
389     *
390     * @param state State of the system when scan was initiated, see WifiMetricsProto.WifiLog.WIFI_X
391     * @param screenOn Is the screen on
392     */
393    public void incrementWifiSystemScanStateCount(int state, boolean screenOn) {
394        synchronized (mLock) {
395            int index = state * (screenOn ? 2 : 1);
396            WifiMetricsProto.WifiLog.WifiSystemStateEntry entry =
397                    mWifiSystemStateEntries.get(index);
398            if (entry == null) {
399                entry = new WifiMetricsProto.WifiLog.WifiSystemStateEntry();
400                entry.wifiState = state;
401                entry.wifiStateCount = 0;
402                entry.isScreenOn = screenOn;
403            }
404            entry.wifiStateCount++;
405            mWifiSystemStateEntries.put(state, entry);
406        }
407    }
408
409    public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
410    /**
411     * Dump all WifiMetrics. Collects some metrics from ConfigStore, Settings and WifiManager
412     * at this time
413     *
414     * @param fd unused
415     * @param pw PrintWriter for writing dump to
416     * @param args unused
417     */
418    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
419        synchronized (mLock) {
420            pw.println("WifiMetrics:");
421            if (args.length > 0 && PROTO_DUMP_ARG.equals(args[0])) {
422                //Dump serialized WifiLog proto
423                consolidateProto(true);
424                for (ConnectionEvent event : mConnectionEventList) {
425                    if (mCurrentConnectionEvent != event) {
426                        //indicate that automatic bug report has been taken for all valid
427                        //connection events
428                        event.mConnectionEvent.automaticBugReportTaken = true;
429                    }
430                }
431                byte[] wifiMetricsProto = WifiMetricsProto.WifiLog.toByteArray(mWifiLogProto);
432                String metricsProtoDump = Base64.encodeToString(wifiMetricsProto, Base64.DEFAULT);
433                pw.println(metricsProtoDump);
434                pw.println("EndWifiMetrics");
435                clear();
436            } else {
437                pw.println("mConnectionEvents:");
438                for (ConnectionEvent event : mConnectionEventList) {
439                    String eventLine = event.toString();
440                    if (event == mCurrentConnectionEvent) {
441                        eventLine += "CURRENTLY OPEN EVENT";
442                    }
443                    pw.println(eventLine);
444                }
445                pw.println("mWifiLogProto.numSavedNetworks=" + mWifiLogProto.numSavedNetworks);
446                pw.println("mWifiLogProto.numOpenNetworks=" + mWifiLogProto.numOpenNetworks);
447                pw.println("mWifiLogProto.numPersonalNetworks="
448                        + mWifiLogProto.numPersonalNetworks);
449                pw.println("mWifiLogProto.numEnterpriseNetworks="
450                        + mWifiLogProto.numEnterpriseNetworks);
451                pw.println("mWifiLogProto.isLocationEnabled=" + mWifiLogProto.isLocationEnabled);
452                pw.println("mWifiLogProto.isScanningAlwaysEnabled="
453                        + mWifiLogProto.isScanningAlwaysEnabled);
454                pw.println("mWifiLogProto.numWifiToggledViaSettings="
455                        + mWifiLogProto.numWifiToggledViaSettings);
456                pw.println("mWifiLogProto.numWifiToggledViaAirplane="
457                        + mWifiLogProto.numWifiToggledViaAirplane);
458                pw.println("mWifiLogProto.numNetworksAddedByUser="
459                        + mWifiLogProto.numNetworksAddedByUser);
460                //TODO - Pending scanning refactor
461                pw.println("mWifiLogProto.numNetworksAddedByApps=" + "<TODO>");
462                pw.println("mWifiLogProto.numNonEmptyScanResults=" + "<TODO>");
463                pw.println("mWifiLogProto.numEmptyScanResults=" + "<TODO>");
464                pw.println("mWifiLogProto.numOneshotScans=" + "<TODO>");
465                pw.println("mWifiLogProto.numBackgroundScans=" + "<TODO>");
466                pw.println("mScanReturnEntries:" + " <TODO>");
467                pw.println("mSystemStateEntries:" + " <TODO>");
468            }
469        }
470    }
471
472    /**
473     * Assign the separate ConnectionEvent, SystemStateEntry and ScanReturnCode lists to their
474     * respective lists within mWifiLogProto, and clear the original lists managed here.
475     *
476     * @param incremental Only include ConnectionEvents created since last automatic bug report
477     */
478    private void consolidateProto(boolean incremental) {
479        List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>();
480        synchronized (mLock) {
481            for (ConnectionEvent event : mConnectionEventList) {
482                if (!incremental || ((mCurrentConnectionEvent != event)
483                        && !event.mConnectionEvent.automaticBugReportTaken)) {
484                    //Get all ConnectionEvents that haven not been dumped as a proto, also exclude
485                    //the current active un-ended connection event
486                    events.add(event.mConnectionEvent);
487                    event.mConnectionEvent.automaticBugReportTaken = true;
488                }
489            }
490            if (events.size() > 0) {
491                mWifiLogProto.connectionEvent = events.toArray(mWifiLogProto.connectionEvent);
492            }
493            //<TODO> SystemStateEntry and ScanReturnCode list consolidation
494        }
495    }
496
497    /**
498     * Serializes all of WifiMetrics to WifiLog proto, and returns the byte array.
499     * Does not count as taking an automatic bug report
500     *
501     * @return byte array of the deserialized & consolidated Proto
502     */
503    public byte[] toByteArray() {
504        consolidateProto(false);
505        return mWifiLogProto.toByteArray(mWifiLogProto);
506    }
507
508    /**
509     * Clear all WifiMetrics, except for currentConnectionEvent.
510     */
511    private void clear() {
512        synchronized (mLock) {
513            mConnectionEventList.clear();
514            if (mCurrentConnectionEvent != null) {
515                mConnectionEventList.add(mCurrentConnectionEvent);
516            }
517            mScanReturnEntries.clear();
518            mWifiSystemStateEntries.clear();
519            mWifiLogProto.clear();
520        }
521    }
522}
523