1 /*
2  * Copyright (C) 2019 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 
17 package com.android.server.wifi.p2p;
18 
19 import android.net.wifi.p2p.WifiP2pConfig;
20 import android.net.wifi.p2p.WifiP2pGroup;
21 import android.net.wifi.p2p.WifiP2pGroupList;
22 import android.util.Log;
23 
24 import com.android.server.wifi.Clock;
25 import com.android.server.wifi.nano.WifiMetricsProto.GroupEvent;
26 import com.android.server.wifi.nano.WifiMetricsProto.P2pConnectionEvent;
27 import com.android.server.wifi.nano.WifiMetricsProto.WifiP2pStats;
28 
29 import java.io.PrintWriter;
30 import java.util.ArrayList;
31 import java.util.Calendar;
32 import java.util.Collection;
33 import java.util.List;
34 
35 /**
36  * Provides storage for wireless connectivity P2p metrics, as they are generated.
37  * Metrics logged by this class include:
38  *   Aggregated connection stats (num of connections, num of failures, ...)
39  *   Discrete connection event stats (time, duration, failure codes, ...)
40  */
41 public class WifiP2pMetrics {
42     private static final String TAG = "WifiP2pMetrics";
43     private static final boolean DBG = false;
44 
45     private static final int MAX_CONNECTION_EVENTS = 256;
46     private static final int MAX_GROUP_EVENTS = 256;
47 
48     private Clock mClock;
49     private final Object mLock = new Object();
50 
51     /**
52      * Metrics are stored within an instance of the WifiP2pStats proto during runtime,
53      * The P2pConnectionEvent and GroupEvent metrics are stored during runtime in member
54      * lists of this WifiP2pMetrics class, with the final WifiLog proto being pieced
55      * together at dump-time
56      */
57     private final WifiP2pStats mWifiP2pStatsProto =
58             new WifiP2pStats();
59 
60     /**
61      * Connection information that gets logged for every P2P connection attempt.
62      */
63     private final List<P2pConnectionEvent> mConnectionEventList =
64             new ArrayList<>();
65 
66     /**
67      * The latest started (but un-ended) connection attempt
68      */
69     private P2pConnectionEvent mCurrentConnectionEvent;
70 
71     /**
72      * The latest started (but un-ended) connection attempt start time
73      */
74     private long mCurrentConnectionEventStartTime;
75 
76     /**
77      * Group Session information that gets logged for every formed group.
78      */
79     private final List<GroupEvent> mGroupEventList =
80             new ArrayList<>();
81 
82     /**
83      * The latest started (but un-ended) group
84      */
85     private GroupEvent mCurrentGroupEvent;
86 
87     /**
88      * The latest started (but un-ended) group start time
89      */
90     private long mCurrentGroupEventStartTime;
91 
92     /**
93      * The latest started (but un-ended) group idle start time.
94      * The group is idle if there is no connected client.
95      */
96     private long mCurrentGroupEventIdleStartTime;
97 
98     /**
99      * The current number of persistent groups.
100      * This should be persisted after a dump.
101      */
102     private int mNumPersistentGroup;
103 
WifiP2pMetrics(Clock clock)104     public WifiP2pMetrics(Clock clock) {
105         mClock = clock;
106 
107         mNumPersistentGroup = 0;
108     }
109 
110     /**
111      * Clear all WifiP2pMetrics, except for currentConnectionEvent.
112      */
clear()113     public void clear() {
114         synchronized (mLock) {
115             mConnectionEventList.clear();
116             if (mCurrentConnectionEvent != null) {
117                 mConnectionEventList.add(mCurrentConnectionEvent);
118             }
119             mGroupEventList.clear();
120             if (mCurrentGroupEvent != null) {
121                 mGroupEventList.add(mCurrentGroupEvent);
122             }
123             mWifiP2pStatsProto.clear();
124         }
125     }
126 
127     /**
128      * Put all metrics that were being tracked separately into mWifiP2pStatsProto
129      */
consolidateProto()130     public WifiP2pStats consolidateProto() {
131         synchronized (mLock) {
132             mWifiP2pStatsProto.numPersistentGroup = mNumPersistentGroup;
133             int connectionEventCount = mConnectionEventList.size();
134             if (mCurrentConnectionEvent != null) {
135                 connectionEventCount--;
136             }
137             mWifiP2pStatsProto.connectionEvent =
138                     new P2pConnectionEvent[connectionEventCount];
139             for (int i = 0; i < connectionEventCount; i++) {
140                 mWifiP2pStatsProto.connectionEvent[i] = mConnectionEventList.get(i);
141             }
142 
143             int groupEventCount = mGroupEventList.size();
144             if (mCurrentGroupEvent != null) {
145                 groupEventCount--;
146             }
147             mWifiP2pStatsProto.groupEvent =
148                     new GroupEvent[groupEventCount];
149             for (int i = 0; i < groupEventCount; i++) {
150                 mWifiP2pStatsProto.groupEvent[i] = mGroupEventList.get(i);
151             }
152             return mWifiP2pStatsProto;
153         }
154     }
155 
156     /**
157      * Dump all WifiP2pMetrics. Collects some metrics at this time.
158      *
159      * @param pw PrintWriter for writing dump to
160      */
dump(PrintWriter pw)161     public void dump(PrintWriter pw) {
162         synchronized (mLock) {
163             pw.println("WifiP2pMetrics:");
164             pw.println("mConnectionEvents:");
165             for (P2pConnectionEvent event : mConnectionEventList) {
166                 StringBuilder sb = new StringBuilder();
167                 Calendar c = Calendar.getInstance();
168                 c.setTimeInMillis(event.startTimeMillis);
169                 sb.append("startTime=");
170                 sb.append(event.startTimeMillis == 0 ? "            <null>" :
171                         String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
172                 sb.append(", connectionType=");
173                 switch (event.connectionType) {
174                     case P2pConnectionEvent.CONNECTION_FRESH:
175                         sb.append("FRESH");
176                         break;
177                     case P2pConnectionEvent.CONNECTION_REINVOKE:
178                         sb.append("REINVOKE");
179                         break;
180                     case P2pConnectionEvent.CONNECTION_LOCAL:
181                         sb.append("LOCAL");
182                         break;
183                     case P2pConnectionEvent.CONNECTION_FAST:
184                         sb.append("FAST");
185                         break;
186                     default:
187                         sb.append("UNKNOWN");
188                         break;
189                 }
190                 sb.append(", wpsMethod=");
191                 switch (event.wpsMethod) {
192                     case P2pConnectionEvent.WPS_NA:
193                         sb.append("NA");
194                         break;
195                     case P2pConnectionEvent.WPS_PBC:
196                         sb.append("PBC");
197                         break;
198                     case P2pConnectionEvent.WPS_DISPLAY:
199                         sb.append("DISPLAY");
200                         break;
201                     case P2pConnectionEvent.WPS_KEYPAD:
202                         sb.append("KEYPAD");
203                         break;
204                     case P2pConnectionEvent.WPS_LABEL:
205                         sb.append("LABLE");
206                         break;
207                     default:
208                         sb.append("UNKNOWN");
209                         break;
210                 }
211                 sb.append(", durationTakenToConnectMillis=");
212                 sb.append(event.durationTakenToConnectMillis);
213                 sb.append(", connectivityLevelFailureCode=");
214                 switch (event.connectivityLevelFailureCode) {
215                     case P2pConnectionEvent.CLF_NONE:
216                         sb.append("NONE");
217                         break;
218                     case P2pConnectionEvent.CLF_TIMEOUT:
219                         sb.append("TIMEOUT");
220                         break;
221                     case P2pConnectionEvent.CLF_CANCEL:
222                         sb.append("CANCEL");
223                         break;
224                     case P2pConnectionEvent.CLF_PROV_DISC_FAIL:
225                         sb.append("PROV_DISC_FAIL");
226                         break;
227                     case P2pConnectionEvent.CLF_INVITATION_FAIL:
228                         sb.append("INVITATION_FAIL");
229                         break;
230                     case P2pConnectionEvent.CLF_USER_REJECT:
231                         sb.append("USER_REJECT");
232                         break;
233                     case P2pConnectionEvent.CLF_NEW_CONNECTION_ATTEMPT:
234                         sb.append("NEW_CONNECTION_ATTEMPT");
235                         break;
236                     case P2pConnectionEvent.CLF_UNKNOWN:
237                     default:
238                         sb.append("UNKNOWN");
239                         break;
240                 }
241 
242                 if (event == mCurrentConnectionEvent) {
243                     sb.append(" CURRENTLY OPEN EVENT");
244                 }
245                 pw.println(sb.toString());
246             }
247             pw.println("mGroupEvents:");
248             for (GroupEvent event : mGroupEventList) {
249                 StringBuilder sb = new StringBuilder();
250                 Calendar c = Calendar.getInstance();
251                 c.setTimeInMillis(event.startTimeMillis);
252                 sb.append("netId=");
253                 sb.append(event.netId);
254                 sb.append(", startTime=");
255                 sb.append(event.startTimeMillis == 0 ? "            <null>" :
256                         String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
257                 sb.append(", channelFrequency=");
258                 sb.append(event.channelFrequency);
259                 sb.append(", groupRole=");
260                 switch (event.groupRole) {
261                     case GroupEvent.GROUP_CLIENT:
262                         sb.append("GroupClient");
263                         break;
264                     case GroupEvent.GROUP_OWNER:
265                     default:
266                         sb.append("GroupOwner");
267                         break;
268                 }
269                 sb.append(", numConnectedClients=");
270                 sb.append(event.numConnectedClients);
271                 sb.append(", numCumulativeClients=");
272                 sb.append(event.numCumulativeClients);
273                 sb.append(", sessionDurationMillis=");
274                 sb.append(event.sessionDurationMillis);
275                 sb.append(", idleDurationMillis=");
276                 sb.append(event.idleDurationMillis);
277 
278                 if (event == mCurrentGroupEvent) {
279                     sb.append(" CURRENTLY OPEN EVENT");
280                 }
281                 pw.println(sb.toString());
282             }
283             pw.println("mWifiP2pStatsProto.numPersistentGroup="
284                     + mNumPersistentGroup);
285             pw.println("mWifiP2pStatsProto.numTotalPeerScans="
286                     + mWifiP2pStatsProto.numTotalPeerScans);
287             pw.println("mWifiP2pStatsProto.numTotalServiceScans="
288                     + mWifiP2pStatsProto.numTotalServiceScans);
289         }
290     }
291 
292     /** Increment total number of peer scans */
incrementPeerScans()293     public void incrementPeerScans() {
294         synchronized (mLock) {
295             mWifiP2pStatsProto.numTotalPeerScans++;
296         }
297     }
298 
299     /** Increment total number of service scans */
incrementServiceScans()300     public void incrementServiceScans() {
301         synchronized (mLock) {
302             mWifiP2pStatsProto.numTotalServiceScans++;
303         }
304     }
305 
306     /** Set the number of saved persistent group */
updatePersistentGroup(WifiP2pGroupList groups)307     public void updatePersistentGroup(WifiP2pGroupList groups) {
308         synchronized (mLock) {
309             final Collection<WifiP2pGroup> list = groups.getGroupList();
310             mNumPersistentGroup = list.size();
311         }
312     }
313 
314     /**
315      * Create a new connection event. Call when p2p attmpts to make a new connection to
316      * another peer. If there is a current 'un-ended' connection event, it will be ended with
317      * P2pConnectionEvent.CLF_NEW_CONNNECTION_ATTEMPT.
318      *
319      * @param connectionType indicate this connection is fresh or reinvoke.
320      * @param config configuration used for this connection.
321      */
startConnectionEvent(int connectionType, WifiP2pConfig config)322     public void startConnectionEvent(int connectionType, WifiP2pConfig config) {
323         synchronized (mLock) {
324             // handle overlapping connection event first.
325             if (mCurrentConnectionEvent != null) {
326                 endConnectionEvent(P2pConnectionEvent.CLF_NEW_CONNECTION_ATTEMPT);
327             }
328 
329             while (mConnectionEventList.size() >= MAX_CONNECTION_EVENTS) {
330                 mConnectionEventList.remove(0);
331             }
332             mCurrentConnectionEventStartTime = mClock.getElapsedSinceBootMillis();
333 
334             mCurrentConnectionEvent = new P2pConnectionEvent();
335             mCurrentConnectionEvent.startTimeMillis = mClock.getWallClockMillis();
336             mCurrentConnectionEvent.connectionType = connectionType;
337             if (config != null) {
338                 mCurrentConnectionEvent.wpsMethod = config.wps.setup;
339             }
340 
341             mConnectionEventList.add(mCurrentConnectionEvent);
342         }
343     }
344 
345     /**
346      * End a Connection event record. Call when p2p connection attempt succeeds or fails.
347      * If a Connection event has not been started when .end is called,
348      * a new one is created with zero duration.
349      *
350      * @param failure indicate the failure with WifiMetricsProto.P2pConnectionEvent.CLF_X.
351      */
endConnectionEvent(int failure)352     public void endConnectionEvent(int failure) {
353         synchronized (mLock) {
354             if (mCurrentConnectionEvent == null) {
355                 // Reinvoking a group with invitation will be handled in supplicant.
356                 // There won't be a connection starting event in framework.
357                 // THe framework only get the connection ending event in GroupStarted state.
358                 startConnectionEvent(P2pConnectionEvent.CONNECTION_REINVOKE, null);
359             }
360 
361             mCurrentConnectionEvent.durationTakenToConnectMillis = (int)
362                     (mClock.getElapsedSinceBootMillis()
363                     - mCurrentConnectionEventStartTime);
364             mCurrentConnectionEvent.connectivityLevelFailureCode = failure;
365 
366             mCurrentConnectionEvent = null;
367         }
368     }
369 
370     /**
371      * Create a new group event.
372      *
373      * @param group the information of started group.
374      */
startGroupEvent(WifiP2pGroup group)375     public void startGroupEvent(WifiP2pGroup group) {
376         if (group == null) {
377             if (DBG) Log.d(TAG, "Cannot start group event due to null group");
378             return;
379         }
380         synchronized (mLock) {
381             // handle overlapping group event first.
382             if (mCurrentGroupEvent != null) {
383                 if (DBG) Log.d(TAG, "Overlapping group event!");
384                 endGroupEvent();
385             }
386 
387             while (mGroupEventList.size() >= MAX_GROUP_EVENTS) {
388                 mGroupEventList.remove(0);
389             }
390             mCurrentGroupEventStartTime = mClock.getElapsedSinceBootMillis();
391             if (group.getClientList().size() == 0) {
392                 mCurrentGroupEventIdleStartTime = mClock.getElapsedSinceBootMillis();
393             } else {
394                 mCurrentGroupEventIdleStartTime = 0;
395             }
396 
397             mCurrentGroupEvent = new GroupEvent();
398             mCurrentGroupEvent.netId = group.getNetworkId();
399             mCurrentGroupEvent.startTimeMillis = mClock.getWallClockMillis();
400             mCurrentGroupEvent.numConnectedClients = group.getClientList().size();
401             mCurrentGroupEvent.channelFrequency = group.getFrequency();
402             mCurrentGroupEvent.groupRole = group.isGroupOwner()
403                     ? GroupEvent.GROUP_OWNER
404                     : GroupEvent.GROUP_CLIENT;
405             mGroupEventList.add(mCurrentGroupEvent);
406         }
407     }
408 
409     /**
410      * Update the information of started group.
411      */
updateGroupEvent(WifiP2pGroup group)412     public void updateGroupEvent(WifiP2pGroup group) {
413         if (group == null) {
414             if (DBG) Log.d(TAG, "Cannot update group event due to null group.");
415             return;
416         }
417         synchronized (mLock) {
418             if (mCurrentGroupEvent == null) {
419                 Log.w(TAG, "Cannot update group event due to no current group.");
420                 return;
421             }
422 
423             if (mCurrentGroupEvent.netId != group.getNetworkId()) {
424                 Log.w(TAG, "Updating group id " + group.getNetworkId()
425                         + " is different from current group id " + mCurrentGroupEvent.netId
426                         + ".");
427                 return;
428             }
429 
430             int delta = group.getClientList().size() - mCurrentGroupEvent.numConnectedClients;
431             mCurrentGroupEvent.numConnectedClients = group.getClientList().size();
432             if (delta > 0) {
433                 mCurrentGroupEvent.numCumulativeClients += delta;
434             }
435 
436             // if new client comes during idle period, cumulate idle duration and reset idle timer.
437             // if the last client disconnected during non-idle period, start idle timer.
438             if (mCurrentGroupEventIdleStartTime > 0) {
439                 if (group.getClientList().size() > 0) {
440                     mCurrentGroupEvent.idleDurationMillis +=
441                             (mClock.getElapsedSinceBootMillis()
442                             - mCurrentGroupEventIdleStartTime);
443                     mCurrentGroupEventIdleStartTime = 0;
444                 }
445             } else {
446                 if (group.getClientList().size() == 0) {
447                     mCurrentGroupEventIdleStartTime = mClock.getElapsedSinceBootMillis();
448                 }
449             }
450         }
451     }
452 
453     /**
454      * End a group event.
455      */
endGroupEvent()456     public void endGroupEvent() {
457         synchronized (mLock) {
458             if (mCurrentGroupEvent != null) {
459                 mCurrentGroupEvent.sessionDurationMillis = (int)
460                         (mClock.getElapsedSinceBootMillis()
461                         - mCurrentGroupEventStartTime);
462                 if (mCurrentGroupEventIdleStartTime > 0) {
463                     mCurrentGroupEvent.idleDurationMillis +=
464                             (mClock.getElapsedSinceBootMillis()
465                             - mCurrentGroupEventIdleStartTime);
466                     mCurrentGroupEventIdleStartTime = 0;
467                 }
468             } else {
469                 Log.e(TAG, "No current group!");
470             }
471             mCurrentGroupEvent = null;
472         }
473     }
474 
475     /* Log Metrics */
476 }
477