WifiMetrics.java revision 1b067831bbff14f8e7a99b927b69f714d1b03448
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.WifiInfo;
20import android.os.SystemClock;
21import android.util.Base64;
22import android.util.SparseArray;
23
24import java.io.FileDescriptor;
25import java.io.PrintWriter;
26import java.util.ArrayList;
27import java.util.Calendar;
28import java.util.List;
29
30/**
31 * Provides storage for wireless connectivity metrics, as they are generated.
32 * Metrics logged by this class include:
33 *   Aggregated connection stats (num of connections, num of failures, ...)
34 *   Discrete connection event stats (time, duration, failure codes, ...)
35 *   Router details (technology type, authentication type, ...)
36 *   Scan stats
37 */
38public class WifiMetrics {
39    private static final String TAG = "WifiMetrics";
40    private final Object mLock = new Object();
41    /**
42     * Metrics are stored within an instance of the WifiLog proto during runtime,
43     * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during
44     * runtime in member lists of this WifiMetrics class, with the final WifiLog proto being pieced
45     * together at dump-time
46     */
47    private final WifiMetricsProto.WifiLog mWifiLogProto;
48    /**
49     * Session information that gets logged for every Wifi connection attempt.
50     */
51    private final List<ConnectionEvent> mConnectionEventList;
52    /**
53     * The latest started (but un-ended) connection attempt
54     */
55    private ConnectionEvent mCurrentConnectionEvent;
56    /**
57     * Count of number of times each scan return code, indexed by WifiLog.ScanReturnCode
58     */
59    private final SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry> mScanReturnEntries;
60    /**
61     * Mapping of system state to the counts of scans requested in that wifi state * screenOn
62     * combination. Indexed by WifiLog.WifiState * (1 + screenOn)
63     */
64    private final SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry>
65            mWifiSystemStateEntries;
66
67    class RouterFingerPrint {
68        private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
69
70        public RouterFingerPrint(int roamType,
71                                 int channelInfo,
72                                 int dtim,
73                                 int authentication,
74                                 boolean hidden,
75                                 int routerTechnology,
76                                 boolean supportsIpv6) {
77            mRouterFingerPrintProto = new WifiMetricsProto.RouterFingerPrint();
78            mRouterFingerPrintProto.roamType = roamType;
79            mRouterFingerPrintProto.channelInfo = channelInfo;
80            mRouterFingerPrintProto.dtim = dtim;
81            mRouterFingerPrintProto.authentication = authentication;
82            mRouterFingerPrintProto.hidden = hidden;
83            mRouterFingerPrintProto.routerTechnology = routerTechnology;
84            mRouterFingerPrintProto.supportsIpv6 = supportsIpv6;
85        }
86
87        public String toString() {
88            StringBuilder sb = new StringBuilder();
89            synchronized (mLock) {
90                sb.append("mConnectionEvent.roamType=" + mRouterFingerPrintProto.roamType);
91                sb.append(", mChannelInfo=" + mRouterFingerPrintProto.channelInfo);
92                sb.append(", mDtim=" + mRouterFingerPrintProto.dtim);
93                sb.append(", mAuthentication=" + mRouterFingerPrintProto.authentication);
94                sb.append(", mHidden=" + mRouterFingerPrintProto.hidden);
95                sb.append(", mRouterTechnology=" + mRouterFingerPrintProto.routerTechnology);
96                sb.append(", mSupportsIpv6=" + mRouterFingerPrintProto.supportsIpv6);
97            }
98            return sb.toString();
99        }
100    }
101
102    /**
103     * Log event, tracking the start time, end time and result of a wireless connection attempt.
104     */
105    class ConnectionEvent {
106        WifiMetricsProto.ConnectionEvent mConnectionEvent;
107        RouterFingerPrint mRouterFingerPrint;
108        private long mRealStartTime;
109        /**
110         * Bitset tracking the capture completeness of this connection event bit 1='Event started',
111         * bit 2='Event ended' value = 3 for capture completeness
112         */
113        private int mEventCompleteness;
114        private long mRealEndTime;
115
116        private ConnectionEvent() {
117            mConnectionEvent = new WifiMetricsProto.ConnectionEvent();
118            mConnectionEvent.startTimeMillis = -1;
119            mRealEndTime = -1;
120            mConnectionEvent.durationTakenToConnectMillis = -1;
121            mRouterFingerPrint = new RouterFingerPrint(0, 0, 0, 0, false, 0, false);
122            mConnectionEvent.signalStrength = -1;
123            mConnectionEvent.roamType = WifiMetricsProto.ConnectionEvent.ROAM_UNKNOWN;
124            mConnectionEvent.connectionResult = -1;
125            mConnectionEvent.level2FailureCode = -1;
126            mConnectionEvent.connectivityLevelFailureCode =
127                    WifiMetricsProto.ConnectionEvent.HLF_UNKNOWN;
128            mConnectionEvent.automaticBugReportTaken = false;
129            mEventCompleteness = 0;
130        }
131
132        public String toString() {
133            StringBuilder sb = new StringBuilder();
134            sb.append("startTime=");
135            Calendar c = Calendar.getInstance();
136            synchronized (mLock) {
137                c.setTimeInMillis(mConnectionEvent.startTimeMillis);
138                sb.append(mConnectionEvent.startTimeMillis == 0 ? "            <null>" :
139                        String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
140                sb.append(", endTime=");
141                c.setTimeInMillis(mRealEndTime);
142                sb.append(mRealEndTime == 0 ? "            <null>" :
143                        String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
144                sb.append(", durationTakenToConnectMillis=");
145                sb.append(mConnectionEvent.durationTakenToConnectMillis);
146                sb.append(", level2FailureCode=");
147                sb.append(mConnectionEvent.level2FailureCode);
148                sb.append(", connectivityLevelFailureCode=");
149                sb.append(mConnectionEvent.connectivityLevelFailureCode);
150                sb.append(", mEventCompleteness=");
151                sb.append(mEventCompleteness);
152                sb.append("\n  ");
153                sb.append("mRouterFingerprint:\n ");
154                sb.append(mRouterFingerPrint.toString());
155            }
156            return sb.toString();
157        }
158    }
159
160    public WifiMetrics() {
161        mWifiLogProto = new WifiMetricsProto.WifiLog();
162        mConnectionEventList = new ArrayList<>();
163        mCurrentConnectionEvent = null;
164        mScanReturnEntries = new SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry>();
165        mWifiSystemStateEntries = new SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry>();
166    }
167
168    /**
169     * Create a new connection event. Call when wifi attempts to make a new network connection
170     * If there is a current 'un-ended' connection event, it will be ended with UNKNOWN connectivity
171     * failure code.
172     * Gathers and sets the RouterFingerPrint data as well
173     *
174     * @param wifiInfo WifiInfo for the current connection attempt, used for connection metrics
175     * @param roamType Roam type that caused connection attempt, see WifiMetricsProto.WifiLog.ROAM_X
176     */
177    public void startConnectionEvent(WifiInfo wifiInfo, int roamType) {
178        synchronized (mLock) {
179            mCurrentConnectionEvent = new ConnectionEvent();
180            mCurrentConnectionEvent.mEventCompleteness |= 1;
181            mCurrentConnectionEvent.mConnectionEvent.startTimeMillis = System.currentTimeMillis();
182            mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
183            //<TODO> Get actual routerfingerprint metrics, not these placeholders
184            mCurrentConnectionEvent.mRouterFingerPrint = new RouterFingerPrint(
185                    WifiMetricsProto.RouterFingerPrint.ROAM_TYPE_UNKNOWN, //TODO
186                    -1, //mChannelInfo TODO
187                    -1, //Dtim TODO
188                    WifiMetricsProto.RouterFingerPrint.AUTH_UNKNOWN, //TODO
189                    false, //mHidden TODO
190                    WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_UNKNOWN, //TODO
191                    false //mSupportsIpv6 TODO
192            );
193            if (wifiInfo != null) {
194                mCurrentConnectionEvent.mConnectionEvent.signalStrength = wifiInfo.getRssi();
195            }
196            mCurrentConnectionEvent.mRealStartTime = SystemClock.elapsedRealtime();
197            mConnectionEventList.add(mCurrentConnectionEvent);
198        }
199    }
200
201    public void startConnectionEvent(WifiInfo wifiInfo) {
202        startConnectionEvent(wifiInfo, WifiMetricsProto.ConnectionEvent.ROAM_NONE);
203    }
204
205    /**
206     * End a Connection event record. Call when wifi connection attempt succeeds or fails.
207     * If a Connection event has not been started and is active when .end is called, a new one is
208     * created with zero duration.
209     *
210     * @param level2FailureCode Level 2 failure code returned by supplicant
211     * @param connectivityFailureCode WifiMetricsProto.ConnectionEvent.HLF_X
212     */
213    public void endConnectionEvent(int level2FailureCode, int connectivityFailureCode) {
214        synchronized (mLock) {
215            if (mCurrentConnectionEvent == null) {
216                //No currentConnectionEvent exists, create an 'un-started' one, and End it
217                mCurrentConnectionEvent = new ConnectionEvent();
218                mConnectionEventList.add(mCurrentConnectionEvent);
219            }
220            mCurrentConnectionEvent.mEventCompleteness |= 2;
221            mCurrentConnectionEvent.mRealEndTime = SystemClock.elapsedRealtime();
222            mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int)
223                    (mCurrentConnectionEvent.mRealEndTime - mCurrentConnectionEvent.mRealStartTime);
224            mCurrentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode;
225            mCurrentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode =
226                    connectivityFailureCode;
227            //ConnectionEvent already added to ConnectionEvents List
228            mCurrentConnectionEvent = null;
229        }
230    }
231
232    void setNumSavedNetworks(int num) {
233        synchronized (mLock) {
234            mWifiLogProto.numSavedNetworks = num;
235        }
236    }
237
238    void setNumOpenNetworks(int num) {
239        synchronized (mLock) {
240            mWifiLogProto.numOpenNetworks = num;
241        }
242    }
243
244    void setNumPersonalNetworks(int num) {
245        synchronized (mLock) {
246            mWifiLogProto.numPersonalNetworks = num;
247        }
248    }
249
250    void setNumEnterpriseNetworks(int num) {
251        synchronized (mLock) {
252            mWifiLogProto.numEnterpriseNetworks = num;
253        }
254    }
255
256    void setNumNetworksAddedByUser(int num) {
257        synchronized (mLock) {
258            mWifiLogProto.numNetworksAddedByUser = num;
259        }
260    }
261
262    void setNumNetworksAddedByApps(int num) {
263        synchronized (mLock) {
264            mWifiLogProto.numNetworksAddedByApps = num;
265        }
266    }
267
268    void setIsLocationEnabled(boolean enabled) {
269        synchronized (mLock) {
270            mWifiLogProto.isLocationEnabled = enabled;
271        }
272    }
273
274    void setIsScanningAlwaysEnabled(boolean enabled) {
275        synchronized (mLock) {
276            mWifiLogProto.isScanningAlwaysEnabled = enabled;
277        }
278    }
279
280    /**
281     * Increment Airplane mode toggle count
282     */
283    public void incrementAirplaneToggleCount() {
284        synchronized (mLock) {
285            mWifiLogProto.numWifiToggledViaAirplane++;
286        }
287    }
288
289    /**
290     * Increment Wifi Toggle count
291     */
292    public void incrementWifiToggleCount() {
293        synchronized (mLock) {
294            mWifiLogProto.numWifiToggledViaSettings++;
295        }
296    }
297
298    /**
299     * Increment Non Empty Scan Results count
300     */
301    public void incrementNonEmptyScanResultCount() {
302        synchronized (mLock) {
303            mWifiLogProto.numNonEmptyScanResults++;
304        }
305    }
306
307    /**
308     * Increment Empty Scan Results count
309     */
310    public void incrementEmptyScanResultCount() {
311        synchronized (mLock) {
312            mWifiLogProto.numEmptyScanResults++;
313        }
314    }
315
316    /**
317     * Increment count of scan return code occurrence
318     *
319     * @param scanReturnCode Return code from scan attempt WifiMetricsProto.WifiLog.SCAN_X
320     */
321    public void incrementScanReturnEntry(int scanReturnCode) {
322        synchronized (mLock) {
323            WifiMetricsProto.WifiLog.ScanReturnEntry entry = mScanReturnEntries.get(scanReturnCode);
324            if (entry == null) {
325                entry = new WifiMetricsProto.WifiLog.ScanReturnEntry();
326                entry.scanReturnCode = scanReturnCode;
327                entry.scanResultsCount = 0;
328            }
329            entry.scanResultsCount++;
330            mScanReturnEntries.put(scanReturnCode, entry);
331        }
332    }
333
334    /**
335     * Increments the count of scans initiated by each wifi state, accounts for screenOn/Off
336     *
337     * @param state State of the system when scan was initiated, see WifiMetricsProto.WifiLog.WIFI_X
338     * @param screenOn Is the screen on
339     */
340    public void incrementWifiSystemScanStateCount(int state, boolean screenOn) {
341        synchronized (mLock) {
342            int index = state * (screenOn ? 2 : 1);
343            WifiMetricsProto.WifiLog.WifiSystemStateEntry entry =
344                    mWifiSystemStateEntries.get(index);
345            if (entry == null) {
346                entry = new WifiMetricsProto.WifiLog.WifiSystemStateEntry();
347                entry.wifiState = state;
348                entry.wifiStateCount = 0;
349                entry.isScreenOn = screenOn;
350            }
351            entry.wifiStateCount++;
352            mWifiSystemStateEntries.put(state, entry);
353        }
354    }
355
356    /**
357     * Dump all WifiMetrics. Collects some metrics from ConfigStore, Settings and WifiManager
358     * at this time
359     *
360     * @param fd unused
361     * @param pw PrintWriter for writing dump to
362     * @param args unused
363     */
364    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
365        synchronized (mLock) {
366            pw.println("WifiMetrics:");
367            if (args.length > 0 && "proto".equals(args[0])) {
368                //Dump serialized WifiLog proto
369                consolidateProto(true);
370                for (ConnectionEvent event : mConnectionEventList) {
371                    if (mCurrentConnectionEvent != event) {
372                        //indicate that automatic bug report has been taken for all valid
373                        //connection events
374                        event.mConnectionEvent.automaticBugReportTaken = true;
375                    }
376                }
377                byte[] wifiMetricsProto = WifiMetricsProto.WifiLog.toByteArray(mWifiLogProto);
378                String metricsProtoDump = Base64.encodeToString(wifiMetricsProto, Base64.DEFAULT);
379                pw.println(metricsProtoDump);
380                pw.println("EndWifiMetrics");
381            } else {
382                pw.println("mConnectionEvents:");
383                for (ConnectionEvent event : mConnectionEventList) {
384                    String eventLine = event.toString();
385                    if (event == mCurrentConnectionEvent) {
386                        eventLine += "CURRENTLY OPEN EVENT";
387                    }
388                    pw.println(eventLine);
389                }
390                pw.println("mWifiLogProto.numSavedNetworks=" + mWifiLogProto.numSavedNetworks);
391                pw.println("mWifiLogProto.numOpenNetworks=" + mWifiLogProto.numOpenNetworks);
392                pw.println("mWifiLogProto.numPersonalNetworks="
393                        + mWifiLogProto.numPersonalNetworks);
394                pw.println("mWifiLogProto.numEnterpriseNetworks="
395                        + mWifiLogProto.numEnterpriseNetworks);
396                pw.println("mWifiLogProto.isLocationEnabled=" + mWifiLogProto.isLocationEnabled);
397                pw.println("mWifiLogProto.isScanningAlwaysEnabled="
398                        + mWifiLogProto.isScanningAlwaysEnabled);
399                pw.println("mWifiLogProto.numWifiToggledViaSettings="
400                        + mWifiLogProto.numWifiToggledViaSettings);
401                pw.println("mWifiLogProto.numWifiToggledViaAirplane="
402                        + mWifiLogProto.numWifiToggledViaAirplane);
403                pw.println("mWifiLogProto.numNetworksAddedByUser="
404                        + mWifiLogProto.numNetworksAddedByUser);
405                //TODO - Pending scanning refactor
406                pw.println("mWifiLogProto.numNetworksAddedByApps=" + "<TODO>");
407                pw.println("mWifiLogProto.numNonEmptyScanResults=" + "<TODO>");
408                pw.println("mWifiLogProto.numEmptyScanResults=" + "<TODO>");
409                pw.println("mWifiLogProto.numOneshotScans=" + "<TODO>");
410                pw.println("mWifiLogProto.numBackgroundScans=" + "<TODO>");
411                pw.println("mScanReturnEntries:" + " <TODO>");
412                pw.println("mSystemStateEntries:" + " <TODO>");
413            }
414        }
415    }
416
417    /**
418     * Assign the separate ConnectionEvent, SystemStateEntry and ScanReturnCode lists to their
419     * respective lists within mWifiLogProto, and clear the original lists managed here.
420     *
421     * @param incremental Only include ConnectionEvents created since last automatic bug report
422     */
423    private void consolidateProto(boolean incremental) {
424        List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>();
425        synchronized (mLock) {
426            for (ConnectionEvent event : mConnectionEventList) {
427                if (!incremental || ((mCurrentConnectionEvent != event)
428                        && !event.mConnectionEvent.automaticBugReportTaken)) {
429                    //<TODO> Revisit logic on when to mark connection events
430                    // as 'automaticBugreportTaken'</TODO>
431                    //Get all ConnectionEvents that haven not been dumped as a proto, also exclude
432                    //the current active un-ended connection event
433                    events.add(event.mConnectionEvent);
434                }
435            }
436            if (events.size() > 0) {
437                mWifiLogProto.connectionEvent = events.toArray(mWifiLogProto.connectionEvent);
438            }
439            //<TODO> SystemStateEntry and ScanReturnCode list consolidation
440        }
441    }
442
443    /**
444     * Serializes all of WifiMetrics to WifiLog proto, and returns the byte array.
445     * Does not count as taking an automatic bug report
446     *
447     * @return byte array of the deserialized & consolidated Proto
448     */
449    public byte[] toByteArray() {
450        consolidateProto(false);
451        return mWifiLogProto.toByteArray(mWifiLogProto);
452    }
453}
454