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 */
16package com.android.server.wifi;
17
18import android.annotation.Nullable;
19import android.os.SystemClock;
20
21import com.android.internal.annotations.GuardedBy;
22import com.android.internal.annotations.VisibleForTesting;
23import com.android.server.wifi.nano.WifiMetricsProto.WifiWakeStats;
24
25import java.io.PrintWriter;
26import java.util.ArrayList;
27import java.util.List;
28
29/**
30 * Holds WifiWake metrics and converts them to a protobuf included in WifiLog.
31 */
32public 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     */
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     */
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     */
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     */
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     */
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     */
156    public void recordIgnoredStart() {
157        mIgnoredStarts++;
158    }
159
160    /**
161     * Returns the consolidated WifiWakeStats proto for WifiMetrics.
162     */
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     */
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     */
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. */
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         */
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         */
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         */
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         */
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         */
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. */
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. */
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
350        public Event(int numScans, long elapsedTime) {
351            mNumScans = numScans;
352            mElapsedTime = elapsedTime;
353        }
354
355        /** Returns the proto representation of this event. */
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
364        public String toString() {
365            return "{ mNumScans: " + mNumScans + ", elapsedTime: " + mElapsedTime + " }";
366        }
367    }
368}
369