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