1 /*
2  * Copyright (C) 2018 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 package com.android.server.wifi;
17 
18 import android.annotation.Nullable;
19 import android.os.SystemClock;
20 
21 import com.android.internal.annotations.GuardedBy;
22 import com.android.internal.annotations.VisibleForTesting;
23 import com.android.server.wifi.nano.WifiMetricsProto.WifiWakeStats;
24 
25 import java.io.PrintWriter;
26 import java.util.ArrayList;
27 import java.util.List;
28 
29 /**
30  * Holds WifiWake metrics and converts them to a protobuf included in WifiLog.
31  */
32 public class WifiWakeMetrics {
33 
34     /** Maximum number of sessions to store in WifiWakeStats proto. */
35     @VisibleForTesting
36     static final int MAX_RECORDED_SESSIONS = 10;
37 
38     @GuardedBy("mLock")
39     private final List<Session> mSessions = new ArrayList<>();
40     @GuardedBy("mLock")
41     private Session mCurrentSession;
42 
43     private boolean mIsInSession = false;
44     private int mTotalSessions = 0;
45     private int mTotalWakeups = 0;
46     private int mIgnoredStarts = 0;
47 
48     private final Object mLock = new Object();
49 
50     /**
51      * Records the beginning of a Wifi Wake session.
52      *
53      * <p>Starts the session.
54      *
55      * @param numNetworks The total number of networks stored in the WakeupLock at start.
56      */
recordStartEvent(int numNetworks)57     public void recordStartEvent(int numNetworks) {
58         synchronized (mLock) {
59             mCurrentSession = new Session(numNetworks, SystemClock.elapsedRealtime());
60             mIsInSession = true;
61         }
62     }
63 
64     /**
65      * Records the initialize event of the current Wifi Wake session.
66      *
67      * <p>Note: The start event must be recorded before this event, otherwise this call will be
68      * ignored.
69      *
70      * @param numScans The total number of elapsed scans since start.
71      * @param numNetworks The total number of networks in the lock.
72      */
recordInitializeEvent(int numScans, int numNetworks)73     public void recordInitializeEvent(int numScans, int numNetworks) {
74         synchronized (mLock) {
75             if (!mIsInSession) {
76                 return;
77             }
78             mCurrentSession.recordInitializeEvent(numScans, numNetworks,
79                     SystemClock.elapsedRealtime());
80         }
81     }
82 
83     /**
84      * Records the unlock event of the current Wifi Wake session.
85      *
86      * <p>The unlock event occurs when the WakeupLock has all of its networks removed. This event
87      * will not be recorded if the initialize event recorded 0 locked networks.
88      *
89      * <p>Note: The start event must be recorded before this event, otherwise this call will be
90      * ignored.
91      *
92      * @param numScans The total number of elapsed scans since start.
93      */
recordUnlockEvent(int numScans)94     public void recordUnlockEvent(int numScans) {
95         synchronized (mLock) {
96             if (!mIsInSession) {
97                 return;
98             }
99             mCurrentSession.recordUnlockEvent(numScans, SystemClock.elapsedRealtime());
100         }
101     }
102 
103     /**
104      * Records the wakeup event of the current Wifi Wake session.
105      *
106      * <p>The wakeup event occurs when Wifi is re-enabled by the WakeupController.
107      *
108      * <p>Note: The start event must be recorded before this event, otherwise this call will be
109      * ignored.
110      *
111      * @param numScans The total number of elapsed scans since start.
112      */
recordWakeupEvent(int numScans)113     public void recordWakeupEvent(int numScans) {
114         synchronized (mLock) {
115             if (!mIsInSession) {
116                 return;
117             }
118             mCurrentSession.recordWakeupEvent(numScans, SystemClock.elapsedRealtime());
119         }
120     }
121 
122     /**
123      * Records the reset event of the current Wifi Wake session.
124      *
125      * <p>The reset event occurs when Wifi enters client mode. Stores the first
126      * {@link #MAX_RECORDED_SESSIONS} in the session list.
127      *
128      * <p>Note: The start event must be recorded before this event, otherwise this call will be
129      * ignored. This event ends the current session.
130      *
131      * @param numScans The total number of elapsed scans since start.
132      */
recordResetEvent(int numScans)133     public void recordResetEvent(int numScans) {
134         synchronized (mLock) {
135             if (!mIsInSession) {
136                 return;
137             }
138             mCurrentSession.recordResetEvent(numScans, SystemClock.elapsedRealtime());
139 
140             // tally successful wakeups here since this is the actual point when wifi is turned on
141             if (mCurrentSession.hasWakeupTriggered()) {
142                 mTotalWakeups++;
143             }
144 
145             mTotalSessions++;
146             if (mSessions.size() < MAX_RECORDED_SESSIONS) {
147                 mSessions.add(mCurrentSession);
148             }
149             mIsInSession = false;
150         }
151     }
152 
153     /**
154      * Records instance of the start event being ignored due to the controller already being active.
155      */
recordIgnoredStart()156     public void recordIgnoredStart() {
157         mIgnoredStarts++;
158     }
159 
160     /**
161      * Returns the consolidated WifiWakeStats proto for WifiMetrics.
162      */
buildProto()163     public WifiWakeStats buildProto() {
164         WifiWakeStats proto = new WifiWakeStats();
165 
166         proto.numSessions = mTotalSessions;
167         proto.numWakeups = mTotalWakeups;
168         proto.numIgnoredStarts = mIgnoredStarts;
169         proto.sessions = new WifiWakeStats.Session[mSessions.size()];
170 
171         for (int i = 0; i < mSessions.size(); i++) {
172             proto.sessions[i] = mSessions.get(i).buildProto();
173         }
174 
175         return proto;
176     }
177 
178     /**
179      * Dump all WifiWake stats to console (pw)
180      * @param pw
181      */
dump(PrintWriter pw)182     public void dump(PrintWriter pw) {
183         synchronized (mLock) {
184             pw.println("-------WifiWake metrics-------");
185             pw.println("mTotalSessions: " + mTotalSessions);
186             pw.println("mTotalWakeups: " + mTotalWakeups);
187             pw.println("mIgnoredStarts: " + mIgnoredStarts);
188             pw.println("mIsInSession: " + mIsInSession);
189             pw.println("Stored Sessions: " + mSessions.size());
190             for (Session session : mSessions) {
191                 session.dump(pw);
192             }
193             if (mCurrentSession != null) {
194                 pw.println("Current Session: ");
195                 mCurrentSession.dump(pw);
196             }
197             pw.println("----end of WifiWake metrics----");
198         }
199     }
200 
201     /**
202      * Clears WifiWakeMetrics.
203      *
204      * <p>Keeps the current WifiWake session.
205      */
clear()206     public void clear() {
207         synchronized (mLock) {
208             mSessions.clear();
209             mTotalSessions = 0;
210             mTotalWakeups = 0;
211             mIgnoredStarts = 0;
212         }
213     }
214 
215     /** A single WifiWake session. */
216     public static class Session {
217 
218         private final long mStartTimestamp;
219         private final int mStartNetworks;
220         private int mInitializeNetworks = 0;
221 
222         @VisibleForTesting
223         @Nullable
224         Event mUnlockEvent;
225         @VisibleForTesting
226         @Nullable
227         Event mInitEvent;
228         @VisibleForTesting
229         @Nullable
230         Event mWakeupEvent;
231         @VisibleForTesting
232         @Nullable
233         Event mResetEvent;
234 
235         /** Creates a new WifiWake session. */
Session(int numNetworks, long timestamp)236         public Session(int numNetworks, long timestamp) {
237             mStartNetworks = numNetworks;
238             mStartTimestamp = timestamp;
239         }
240 
241         /**
242          * Records an initialize event.
243          *
244          * <p>Ignores subsequent calls.
245          *
246          * @param numScans Total number of scans at the time of this event.
247          * @param numNetworks Total number of networks in the lock.
248          * @param timestamp The timestamp of the event.
249          */
recordInitializeEvent(int numScans, int numNetworks, long timestamp)250         public void recordInitializeEvent(int numScans, int numNetworks, long timestamp) {
251             if (mInitEvent == null) {
252                 mInitializeNetworks = numNetworks;
253                 mInitEvent = new Event(numScans, timestamp - mStartTimestamp);
254             }
255         }
256 
257         /**
258          * Records an unlock event.
259          *
260          * <p>Ignores subsequent calls.
261          *
262          * @param numScans Total number of scans at the time of this event.
263          * @param timestamp The timestamp of the event.
264          */
recordUnlockEvent(int numScans, long timestamp)265         public void recordUnlockEvent(int numScans, long timestamp) {
266             if (mUnlockEvent == null) {
267                 mUnlockEvent = new Event(numScans, timestamp - mStartTimestamp);
268             }
269         }
270 
271         /**
272          * Records a wakeup event.
273          *
274          * <p>Ignores subsequent calls.
275          *
276          * @param numScans Total number of scans at the time of this event.
277          * @param timestamp The timestamp of the event.
278          */
recordWakeupEvent(int numScans, long timestamp)279         public void recordWakeupEvent(int numScans, long timestamp) {
280             if (mWakeupEvent == null) {
281                 mWakeupEvent = new Event(numScans, timestamp - mStartTimestamp);
282             }
283         }
284 
285         /**
286          * Returns whether the current session has had its wakeup event triggered.
287          */
hasWakeupTriggered()288         public boolean hasWakeupTriggered() {
289             return mWakeupEvent != null;
290         }
291 
292         /**
293          * Records a reset event.
294          *
295          * <p>Ignores subsequent calls.
296          *
297          * @param numScans Total number of scans at the time of this event.
298          * @param timestamp The timestamp of the event.
299          */
recordResetEvent(int numScans, long timestamp)300         public void recordResetEvent(int numScans, long timestamp) {
301             if (mResetEvent == null) {
302                 mResetEvent = new Event(numScans, timestamp - mStartTimestamp);
303             }
304         }
305 
306         /** Returns the proto representation of this session. */
buildProto()307         public WifiWakeStats.Session buildProto() {
308             WifiWakeStats.Session sessionProto = new WifiWakeStats.Session();
309             sessionProto.startTimeMillis = mStartTimestamp;
310             sessionProto.lockedNetworksAtStart = mStartNetworks;
311 
312             if (mInitEvent != null) {
313                 sessionProto.lockedNetworksAtInitialize = mInitializeNetworks;
314                 sessionProto.initializeEvent = mInitEvent.buildProto();
315             }
316             if (mUnlockEvent != null) {
317                 sessionProto.unlockEvent = mUnlockEvent.buildProto();
318             }
319             if (mWakeupEvent != null) {
320                 sessionProto.wakeupEvent = mWakeupEvent.buildProto();
321             }
322             if (mResetEvent != null) {
323                 sessionProto.resetEvent = mResetEvent.buildProto();
324             }
325 
326             return sessionProto;
327         }
328 
329         /** Dumps the current state of the session. */
dump(PrintWriter pw)330         public void dump(PrintWriter pw) {
331             pw.println("WifiWakeMetrics.Session:");
332             pw.println("mStartTimestamp: " + mStartTimestamp);
333             pw.println("mStartNetworks: " + mStartNetworks);
334             pw.println("mInitializeNetworks: " + mInitializeNetworks);
335             pw.println("mInitEvent: " + (mInitEvent == null ? "{}" : mInitEvent.toString()));
336             pw.println("mUnlockEvent: " + (mUnlockEvent == null ? "{}" : mUnlockEvent.toString()));
337             pw.println("mWakeupEvent: " + (mWakeupEvent == null ? "{}" : mWakeupEvent.toString()));
338             pw.println("mResetEvent: " + (mResetEvent == null ? "{}" : mResetEvent.toString()));
339         }
340     }
341 
342     /** An event in a WifiWake session. */
343     public static class Event {
344 
345         /** Total number of scans that have elapsed prior to this event. */
346         public final int mNumScans;
347         /** Total elapsed time in milliseconds at the instant of this event. */
348         public final long mElapsedTime;
349 
Event(int numScans, long elapsedTime)350         public Event(int numScans, long elapsedTime) {
351             mNumScans = numScans;
352             mElapsedTime = elapsedTime;
353         }
354 
355         /** Returns the proto representation of this event. */
buildProto()356         public WifiWakeStats.Session.Event buildProto() {
357             WifiWakeStats.Session.Event eventProto = new WifiWakeStats.Session.Event();
358             eventProto.elapsedScans = mNumScans;
359             eventProto.elapsedTimeMillis = mElapsedTime;
360             return eventProto;
361         }
362 
363         @Override
toString()364         public String toString() {
365             return "{ mNumScans: " + mNumScans + ", elapsedTime: " + mElapsedTime + " }";
366         }
367     }
368 }
369