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.proto.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