1 /* 2 * Copyright (C) 2022 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.power.stats.wakeups; 18 19 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM; 20 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA; 21 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR; 22 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER; 23 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN; 24 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI; 25 26 import android.annotation.SuppressLint; 27 import android.app.ActivityManager; 28 import android.content.Context; 29 import android.os.Handler; 30 import android.os.HandlerExecutor; 31 import android.os.Trace; 32 import android.os.UserHandle; 33 import android.provider.DeviceConfig; 34 import android.util.IndentingPrintWriter; 35 import android.util.LongSparseArray; 36 import android.util.Slog; 37 import android.util.SparseArray; 38 import android.util.SparseBooleanArray; 39 import android.util.SparseIntArray; 40 import android.util.SparseLongArray; 41 import android.util.TimeUtils; 42 43 import com.android.internal.annotations.VisibleForTesting; 44 import com.android.internal.util.ArrayUtils; 45 import com.android.internal.util.FrameworkStatsLog; 46 import com.android.internal.util.IntPair; 47 48 import java.util.Arrays; 49 import java.util.List; 50 import java.util.concurrent.Executor; 51 import java.util.concurrent.TimeUnit; 52 import java.util.function.LongSupplier; 53 import java.util.regex.Matcher; 54 import java.util.regex.Pattern; 55 56 /** 57 * Stores stats about CPU wakeups and tries to attribute them to subsystems and uids. 58 */ 59 public class CpuWakeupStats { 60 private static final String TAG = "CpuWakeupStats"; 61 private static final String SUBSYSTEM_ALARM_STRING = "Alarm"; 62 private static final String SUBSYSTEM_WIFI_STRING = "Wifi"; 63 private static final String SUBSYSTEM_SOUND_TRIGGER_STRING = "Sound_trigger"; 64 private static final String SUBSYSTEM_SENSOR_STRING = "Sensor"; 65 private static final String SUBSYSTEM_CELLULAR_DATA_STRING = "Cellular_data"; 66 private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution"; 67 68 private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.SECONDS.toMillis(30); 69 70 private final Handler mHandler; 71 private final IrqDeviceMap mIrqDeviceMap; 72 @VisibleForTesting 73 final Config mConfig = new Config(); 74 private final WakingActivityHistory mRecentWakingActivity; 75 76 @VisibleForTesting 77 final LongSparseArray<Wakeup> mWakeupEvents = new LongSparseArray<>(); 78 79 /* Maps timestamp -> {subsystem -> {uid -> procState}} */ 80 @VisibleForTesting 81 final LongSparseArray<SparseArray<SparseIntArray>> mWakeupAttribution = 82 new LongSparseArray<>(); 83 84 final SparseIntArray mUidProcStates = new SparseIntArray(); 85 private final SparseIntArray mReusableUidProcStates = new SparseIntArray(4); 86 CpuWakeupStats(Context context, int mapRes, Handler handler)87 public CpuWakeupStats(Context context, int mapRes, Handler handler) { 88 mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes); 89 mRecentWakingActivity = new WakingActivityHistory( 90 () -> mConfig.WAKING_ACTIVITY_RETENTION_MS); 91 mHandler = handler; 92 } 93 94 /** 95 * Called on the boot phase SYSTEM_SERVICES_READY. 96 * This ensures that DeviceConfig is ready for calls to read properties. 97 */ systemServicesReady()98 public synchronized void systemServicesReady() { 99 mConfig.register(new HandlerExecutor(mHandler)); 100 } 101 typeToStatsType(int wakeupType)102 private static int typeToStatsType(int wakeupType) { 103 switch (wakeupType) { 104 case Wakeup.TYPE_ABNORMAL: 105 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_ABNORMAL; 106 case Wakeup.TYPE_IRQ: 107 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ; 108 } 109 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN; 110 } 111 subsystemToStatsReason(int subsystem)112 private static int subsystemToStatsReason(int subsystem) { 113 switch (subsystem) { 114 case CPU_WAKEUP_SUBSYSTEM_ALARM: 115 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__ALARM; 116 case CPU_WAKEUP_SUBSYSTEM_WIFI: 117 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__WIFI; 118 case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER: 119 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__SOUND_TRIGGER; 120 case CPU_WAKEUP_SUBSYSTEM_SENSOR: 121 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__SENSOR; 122 case CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA: 123 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__CELLULAR_DATA; 124 } 125 return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN; 126 } 127 logWakeupAttribution(Wakeup wakeupToLog)128 private synchronized void logWakeupAttribution(Wakeup wakeupToLog) { 129 if (ArrayUtils.isEmpty(wakeupToLog.mDevices)) { 130 FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED, 131 FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN, 132 FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN, 133 null, 134 wakeupToLog.mElapsedMillis, 135 null); 136 Trace.instantForTrack(Trace.TRACE_TAG_POWER, TRACE_TRACK_WAKEUP_ATTRIBUTION, 137 wakeupToLog.mElapsedMillis + " --"); 138 return; 139 } 140 141 final SparseArray<SparseIntArray> wakeupAttribution = mWakeupAttribution.get( 142 wakeupToLog.mElapsedMillis); 143 if (wakeupAttribution == null) { 144 // This is not expected but can theoretically happen in extreme situations, e.g. if we 145 // remove the wakeup before the handler gets to process this message. 146 Slog.wtf(TAG, "Unexpected null attribution found for " + wakeupToLog); 147 return; 148 } 149 150 final StringBuilder traceEventBuilder = new StringBuilder(); 151 152 for (int i = 0; i < wakeupAttribution.size(); i++) { 153 final int subsystem = wakeupAttribution.keyAt(i); 154 final SparseIntArray uidProcStates = wakeupAttribution.valueAt(i); 155 final int[] uids; 156 final int[] procStatesProto; 157 158 if (uidProcStates == null || uidProcStates.size() == 0) { 159 uids = procStatesProto = new int[0]; 160 } else { 161 final int numUids = uidProcStates.size(); 162 uids = new int[numUids]; 163 procStatesProto = new int[numUids]; 164 for (int j = 0; j < numUids; j++) { 165 uids[j] = uidProcStates.keyAt(j); 166 procStatesProto[j] = ActivityManager.processStateAmToProto( 167 uidProcStates.valueAt(j)); 168 } 169 } 170 FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED, 171 typeToStatsType(wakeupToLog.mType), 172 subsystemToStatsReason(subsystem), 173 uids, 174 wakeupToLog.mElapsedMillis, 175 procStatesProto); 176 177 if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) { 178 if (i == 0) { 179 traceEventBuilder.append(wakeupToLog.mElapsedMillis + " "); 180 } 181 traceEventBuilder.append((subsystemToString(subsystem))); 182 traceEventBuilder.append(":"); 183 traceEventBuilder.append(Arrays.toString(uids)); 184 traceEventBuilder.append(" "); 185 } 186 } 187 Trace.instantForTrack(Trace.TRACE_TAG_POWER, TRACE_TRACK_WAKEUP_ATTRIBUTION, 188 traceEventBuilder.toString().trim()); 189 } 190 191 /** 192 * Clean up data for a uid that is being removed. 193 */ onUidRemoved(int uid)194 public synchronized void onUidRemoved(int uid) { 195 mUidProcStates.delete(uid); 196 } 197 198 /** 199 * Notes a procstate change for the given uid to maintain the mapping internally. 200 */ noteUidProcessState(int uid, int state)201 public synchronized void noteUidProcessState(int uid, int state) { 202 mUidProcStates.put(uid, state); 203 } 204 205 /** Notes a wakeup reason as reported by SuspendControlService to battery stats. */ noteWakeupTimeAndReason(long elapsedRealtime, long uptime, String rawReason)206 public synchronized void noteWakeupTimeAndReason(long elapsedRealtime, long uptime, 207 String rawReason) { 208 final Wakeup parsedWakeup = Wakeup.parseWakeup(rawReason, elapsedRealtime, uptime, 209 mIrqDeviceMap); 210 if (parsedWakeup == null) { 211 // This wakeup is unsupported for attribution. Exit. 212 return; 213 } 214 mWakeupEvents.put(elapsedRealtime, parsedWakeup); 215 attemptAttributionFor(parsedWakeup); 216 217 // Limit history of wakeups and their attribution to the last retentionDuration. Note that 218 // the last wakeup and its attribution (if computed) is always stored, even if that wakeup 219 // had occurred before retentionDuration. 220 final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS; 221 int lastIdx = mWakeupEvents.lastIndexOnOrBefore(elapsedRealtime - retentionDuration); 222 for (int i = lastIdx; i >= 0; i--) { 223 mWakeupEvents.removeAt(i); 224 } 225 lastIdx = mWakeupAttribution.lastIndexOnOrBefore(elapsedRealtime - retentionDuration); 226 for (int i = lastIdx; i >= 0; i--) { 227 mWakeupAttribution.removeAt(i); 228 } 229 mHandler.postDelayed(() -> logWakeupAttribution(parsedWakeup), WAKEUP_WRITE_DELAY_MS); 230 } 231 232 /** Notes a waking activity that could have potentially woken up the CPU. */ noteWakingActivity(int subsystem, long elapsedRealtime, int... uids)233 public synchronized void noteWakingActivity(int subsystem, long elapsedRealtime, int... uids) { 234 if (uids == null) { 235 return; 236 } 237 mReusableUidProcStates.clear(); 238 for (int i = 0; i < uids.length; i++) { 239 mReusableUidProcStates.put(uids[i], 240 mUidProcStates.get(uids[i], ActivityManager.PROCESS_STATE_UNKNOWN)); 241 } 242 if (!attemptAttributionWith(subsystem, elapsedRealtime, mReusableUidProcStates)) { 243 mRecentWakingActivity.recordActivity(subsystem, elapsedRealtime, 244 mReusableUidProcStates); 245 } 246 } 247 attemptAttributionFor(Wakeup wakeup)248 private synchronized void attemptAttributionFor(Wakeup wakeup) { 249 final SparseBooleanArray subsystems = wakeup.mResponsibleSubsystems; 250 251 SparseArray<SparseIntArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis); 252 if (attribution == null) { 253 attribution = new SparseArray<>(); 254 mWakeupAttribution.put(wakeup.mElapsedMillis, attribution); 255 } 256 final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS; 257 258 for (int subsystemIdx = 0; subsystemIdx < subsystems.size(); subsystemIdx++) { 259 final int subsystem = subsystems.keyAt(subsystemIdx); 260 261 // Blame all activity that happened matchingWindowMillis before or after 262 // the wakeup from each responsible subsystem. 263 final long startTime = wakeup.mElapsedMillis - matchingWindowMillis; 264 final long endTime = wakeup.mElapsedMillis + matchingWindowMillis; 265 266 final SparseIntArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem, 267 startTime, endTime); 268 attribution.put(subsystem, uidsToBlame); 269 } 270 } 271 attemptAttributionWith(int subsystem, long activityElapsed, SparseIntArray uidProcStates)272 private synchronized boolean attemptAttributionWith(int subsystem, long activityElapsed, 273 SparseIntArray uidProcStates) { 274 final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS; 275 276 final int startIdx = mWakeupEvents.firstIndexOnOrAfter( 277 activityElapsed - matchingWindowMillis); 278 final int endIdx = mWakeupEvents.lastIndexOnOrBefore( 279 activityElapsed + matchingWindowMillis); 280 281 for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) { 282 final Wakeup wakeup = mWakeupEvents.valueAt(wakeupIdx); 283 final SparseBooleanArray subsystems = wakeup.mResponsibleSubsystems; 284 if (subsystems.get(subsystem)) { 285 // We don't expect more than one wakeup to be found within such a short window, so 286 // just attribute this one and exit 287 SparseArray<SparseIntArray> attribution = mWakeupAttribution.get( 288 wakeup.mElapsedMillis); 289 if (attribution == null) { 290 attribution = new SparseArray<>(); 291 mWakeupAttribution.put(wakeup.mElapsedMillis, attribution); 292 } 293 SparseIntArray uidsToBlame = attribution.get(subsystem); 294 if (uidsToBlame == null) { 295 attribution.put(subsystem, uidProcStates.clone()); 296 } else { 297 for (int i = 0; i < uidProcStates.size(); i++) { 298 uidsToBlame.put(uidProcStates.keyAt(i), uidProcStates.valueAt(i)); 299 } 300 } 301 return true; 302 } 303 } 304 return false; 305 } 306 307 /** Dumps the relevant stats for cpu wakeups and their attribution to subsystem and uids */ dump(IndentingPrintWriter pw, long nowElapsed)308 public synchronized void dump(IndentingPrintWriter pw, long nowElapsed) { 309 pw.println("CPU wakeup stats:"); 310 pw.increaseIndent(); 311 312 mConfig.dump(pw); 313 pw.println(); 314 315 mIrqDeviceMap.dump(pw); 316 pw.println(); 317 318 mRecentWakingActivity.dump(pw, nowElapsed); 319 pw.println(); 320 321 pw.println("Current proc-state map (" + mUidProcStates.size() + "):"); 322 pw.increaseIndent(); 323 for (int i = 0; i < mUidProcStates.size(); i++) { 324 if (i > 0) { 325 pw.print(", "); 326 } 327 UserHandle.formatUid(pw, mUidProcStates.keyAt(i)); 328 pw.print(":" + ActivityManager.procStateToString(mUidProcStates.valueAt(i))); 329 } 330 pw.println(); 331 pw.decreaseIndent(); 332 pw.println(); 333 334 final SparseLongArray attributionStats = new SparseLongArray(); 335 pw.println("Wakeup events:"); 336 pw.increaseIndent(); 337 for (int i = mWakeupEvents.size() - 1; i >= 0; i--) { 338 TimeUtils.formatDuration(mWakeupEvents.keyAt(i), nowElapsed, pw); 339 pw.println(":"); 340 341 pw.increaseIndent(); 342 final Wakeup wakeup = mWakeupEvents.valueAt(i); 343 pw.println(wakeup); 344 pw.print("Attribution: "); 345 final SparseArray<SparseIntArray> attribution = mWakeupAttribution.get( 346 wakeup.mElapsedMillis); 347 if (attribution == null) { 348 pw.println("N/A"); 349 } else { 350 for (int subsystemIdx = 0; subsystemIdx < attribution.size(); subsystemIdx++) { 351 if (subsystemIdx > 0) { 352 pw.print(", "); 353 } 354 final long counters = attributionStats.get(attribution.keyAt(subsystemIdx), 355 IntPair.of(0, 0)); 356 int attributed = IntPair.first(counters); 357 final int total = IntPair.second(counters) + 1; 358 359 pw.print(subsystemToString(attribution.keyAt(subsystemIdx))); 360 pw.print(" ["); 361 final SparseIntArray uidProcStates = attribution.valueAt(subsystemIdx); 362 if (uidProcStates != null) { 363 for (int uidIdx = 0; uidIdx < uidProcStates.size(); uidIdx++) { 364 if (uidIdx > 0) { 365 pw.print(", "); 366 } 367 UserHandle.formatUid(pw, uidProcStates.keyAt(uidIdx)); 368 pw.print(" " + ActivityManager.procStateToString( 369 uidProcStates.valueAt(uidIdx))); 370 } 371 attributed++; 372 } 373 pw.print("]"); 374 375 attributionStats.put(attribution.keyAt(subsystemIdx), 376 IntPair.of(attributed, total)); 377 } 378 pw.println(); 379 } 380 pw.decreaseIndent(); 381 } 382 pw.decreaseIndent(); 383 384 pw.println("Attribution stats:"); 385 pw.increaseIndent(); 386 for (int i = 0; i < attributionStats.size(); i++) { 387 pw.print("Subsystem " + subsystemToString(attributionStats.keyAt(i))); 388 pw.print(": "); 389 final long ratio = attributionStats.valueAt(i); 390 pw.println(IntPair.first(ratio) + "/" + IntPair.second(ratio)); 391 } 392 pw.println("Total: " + mWakeupEvents.size()); 393 pw.decreaseIndent(); 394 395 pw.decreaseIndent(); 396 pw.println(); 397 } 398 399 /** 400 * This class stores recent unattributed activity history per subsystem. 401 * The activity is stored as a mapping of subsystem to timestamp to uid to procstate. 402 */ 403 @VisibleForTesting 404 static final class WakingActivityHistory { 405 private LongSupplier mRetentionSupplier; 406 @VisibleForTesting 407 final SparseArray<LongSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>(); 408 WakingActivityHistory(LongSupplier retentionSupplier)409 WakingActivityHistory(LongSupplier retentionSupplier) { 410 mRetentionSupplier = retentionSupplier; 411 } 412 recordActivity(int subsystem, long elapsedRealtime, SparseIntArray uidProcStates)413 void recordActivity(int subsystem, long elapsedRealtime, SparseIntArray uidProcStates) { 414 if (uidProcStates == null) { 415 return; 416 } 417 LongSparseArray<SparseIntArray> wakingActivity = mWakingActivity.get(subsystem); 418 if (wakingActivity == null) { 419 wakingActivity = new LongSparseArray<>(); 420 mWakingActivity.put(subsystem, wakingActivity); 421 } 422 final SparseIntArray uidsToBlame = wakingActivity.get(elapsedRealtime); 423 if (uidsToBlame == null) { 424 wakingActivity.put(elapsedRealtime, uidProcStates.clone()); 425 } else { 426 for (int i = 0; i < uidProcStates.size(); i++) { 427 final int uid = uidProcStates.keyAt(i); 428 // Just in case there are duplicate uids reported with the same timestamp, 429 // keep the processState which was reported first. 430 if (uidsToBlame.indexOfKey(uid) < 0) { 431 uidsToBlame.put(uid, uidProcStates.valueAt(i)); 432 } 433 } 434 } 435 // Limit activity history per subsystem to the last retention period as supplied by 436 // mRetentionSupplier. Note that the last activity is always present, even if it 437 // occurred before the retention period. 438 final int endIdx = wakingActivity.lastIndexOnOrBefore( 439 elapsedRealtime - mRetentionSupplier.getAsLong()); 440 for (int i = endIdx; i >= 0; i--) { 441 wakingActivity.removeAt(i); 442 } 443 } 444 removeBetween(int subsystem, long startElapsed, long endElapsed)445 SparseIntArray removeBetween(int subsystem, long startElapsed, long endElapsed) { 446 final SparseIntArray uidsToReturn = new SparseIntArray(); 447 448 final LongSparseArray<SparseIntArray> activityForSubsystem = 449 mWakingActivity.get(subsystem); 450 if (activityForSubsystem != null) { 451 final int startIdx = activityForSubsystem.firstIndexOnOrAfter(startElapsed); 452 final int endIdx = activityForSubsystem.lastIndexOnOrBefore(endElapsed); 453 for (int i = endIdx; i >= startIdx; i--) { 454 final SparseIntArray uidsForTime = activityForSubsystem.valueAt(i); 455 for (int j = 0; j < uidsForTime.size(); j++) { 456 // In case the same uid appears in different uidsForTime maps, there is no 457 // good way to choose one processState, so just arbitrarily pick any. 458 uidsToReturn.put(uidsForTime.keyAt(j), uidsForTime.valueAt(j)); 459 } 460 } 461 // More efficient to remove in a separate loop as it avoids repeatedly calling gc(). 462 for (int i = endIdx; i >= startIdx; i--) { 463 activityForSubsystem.removeAt(i); 464 } 465 // Generally waking activity is a high frequency occurrence for any subsystem, so we 466 // don't delete the LongSparseArray even if it is now empty, to avoid object churn. 467 // This will leave one LongSparseArray per subsystem, which are few right now. 468 } 469 return uidsToReturn.size() > 0 ? uidsToReturn : null; 470 } 471 dump(IndentingPrintWriter pw, long nowElapsed)472 void dump(IndentingPrintWriter pw, long nowElapsed) { 473 pw.println("Recent waking activity:"); 474 pw.increaseIndent(); 475 for (int i = 0; i < mWakingActivity.size(); i++) { 476 pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":"); 477 final LongSparseArray<SparseIntArray> wakingActivity = mWakingActivity.valueAt(i); 478 if (wakingActivity == null) { 479 continue; 480 } 481 pw.increaseIndent(); 482 for (int j = wakingActivity.size() - 1; j >= 0; j--) { 483 TimeUtils.formatDuration(wakingActivity.keyAt(j), nowElapsed, pw); 484 final SparseIntArray uidsToBlame = wakingActivity.valueAt(j); 485 if (uidsToBlame == null) { 486 pw.println(); 487 continue; 488 } 489 pw.print(": "); 490 for (int k = 0; k < uidsToBlame.size(); k++) { 491 UserHandle.formatUid(pw, uidsToBlame.keyAt(k)); 492 pw.print(" [" + ActivityManager.procStateToString(uidsToBlame.valueAt(k))); 493 pw.print("], "); 494 } 495 pw.println(); 496 } 497 pw.decreaseIndent(); 498 } 499 pw.decreaseIndent(); 500 } 501 } 502 stringToKnownSubsystem(String rawSubsystem)503 static int stringToKnownSubsystem(String rawSubsystem) { 504 switch (rawSubsystem) { 505 case SUBSYSTEM_ALARM_STRING: 506 return CPU_WAKEUP_SUBSYSTEM_ALARM; 507 case SUBSYSTEM_WIFI_STRING: 508 return CPU_WAKEUP_SUBSYSTEM_WIFI; 509 case SUBSYSTEM_SOUND_TRIGGER_STRING: 510 return CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER; 511 case SUBSYSTEM_SENSOR_STRING: 512 return CPU_WAKEUP_SUBSYSTEM_SENSOR; 513 case SUBSYSTEM_CELLULAR_DATA_STRING: 514 return CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA; 515 } 516 return CPU_WAKEUP_SUBSYSTEM_UNKNOWN; 517 } 518 subsystemToString(int subsystem)519 static String subsystemToString(int subsystem) { 520 switch (subsystem) { 521 case CPU_WAKEUP_SUBSYSTEM_ALARM: 522 return SUBSYSTEM_ALARM_STRING; 523 case CPU_WAKEUP_SUBSYSTEM_WIFI: 524 return SUBSYSTEM_WIFI_STRING; 525 case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER: 526 return SUBSYSTEM_SOUND_TRIGGER_STRING; 527 case CPU_WAKEUP_SUBSYSTEM_SENSOR: 528 return SUBSYSTEM_SENSOR_STRING; 529 case CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA: 530 return SUBSYSTEM_CELLULAR_DATA_STRING; 531 case CPU_WAKEUP_SUBSYSTEM_UNKNOWN: 532 return "Unknown"; 533 } 534 return "N/A"; 535 } 536 537 @VisibleForTesting 538 static final class Wakeup { 539 private static final String PARSER_TAG = "CpuWakeupStats.Wakeup"; 540 private static final String ABORT_REASON_PREFIX = "Abort"; 541 private static final Pattern sIrqPattern = Pattern.compile("^(\\-?\\d+)\\s+(\\S+)"); 542 543 /** 544 * Classical interrupts, which arrive on a dedicated GPIO pin into the main CPU. 545 * Sometimes, when multiple IRQs happen close to each other, they may get batched together. 546 */ 547 static final int TYPE_IRQ = 1; 548 549 /** 550 * Non-IRQ wakeups. The exact mechanism for these is unknown, except that these explicitly 551 * do not use an interrupt line or a GPIO pin. 552 */ 553 static final int TYPE_ABNORMAL = 2; 554 555 int mType; 556 long mElapsedMillis; 557 long mUptimeMillis; 558 IrqDevice[] mDevices; 559 SparseBooleanArray mResponsibleSubsystems; 560 Wakeup(int type, IrqDevice[] devices, long elapsedMillis, long uptimeMillis, SparseBooleanArray responsibleSubsystems)561 private Wakeup(int type, IrqDevice[] devices, long elapsedMillis, long uptimeMillis, 562 SparseBooleanArray responsibleSubsystems) { 563 mType = type; 564 mDevices = devices; 565 mElapsedMillis = elapsedMillis; 566 mUptimeMillis = uptimeMillis; 567 mResponsibleSubsystems = responsibleSubsystems; 568 } 569 parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis, IrqDeviceMap deviceMap)570 static Wakeup parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis, 571 IrqDeviceMap deviceMap) { 572 final String[] components = rawReason.split(":"); 573 if (ArrayUtils.isEmpty(components) || components[0].startsWith(ABORT_REASON_PREFIX)) { 574 // Accounting of aborts is not supported yet. 575 return null; 576 } 577 578 int type = TYPE_IRQ; 579 int parsedDeviceCount = 0; 580 final IrqDevice[] parsedDevices = new IrqDevice[components.length]; 581 final SparseBooleanArray responsibleSubsystems = new SparseBooleanArray(); 582 583 for (String component : components) { 584 final Matcher matcher = sIrqPattern.matcher(component.trim()); 585 if (matcher.find()) { 586 final int line; 587 final String device; 588 try { 589 line = Integer.parseInt(matcher.group(1)); 590 device = matcher.group(2); 591 if (line < 0) { 592 // Assuming that IRQ wakeups cannot come batched with non-IRQ wakeups. 593 type = TYPE_ABNORMAL; 594 } 595 } catch (NumberFormatException e) { 596 Slog.e(PARSER_TAG, 597 "Exception while parsing device names from part: " + component, e); 598 continue; 599 } 600 parsedDevices[parsedDeviceCount++] = new IrqDevice(line, device); 601 602 final List<String> rawSubsystems = deviceMap.getSubsystemsForDevice(device); 603 boolean anyKnownSubsystem = false; 604 if (rawSubsystems != null) { 605 for (int i = 0; i < rawSubsystems.size(); i++) { 606 final int subsystem = stringToKnownSubsystem(rawSubsystems.get(i)); 607 if (subsystem != CPU_WAKEUP_SUBSYSTEM_UNKNOWN) { 608 // Just in case the xml had arbitrary subsystem names, we want to 609 // make sure that we only put the known ones into our map. 610 responsibleSubsystems.put(subsystem, true); 611 anyKnownSubsystem = true; 612 } 613 } 614 } 615 if (!anyKnownSubsystem) { 616 responsibleSubsystems.put(CPU_WAKEUP_SUBSYSTEM_UNKNOWN, true); 617 } 618 } 619 } 620 if (parsedDeviceCount == 0) { 621 return null; 622 } 623 if (responsibleSubsystems.size() == 1 && responsibleSubsystems.get( 624 CPU_WAKEUP_SUBSYSTEM_UNKNOWN, false)) { 625 // There is no attributable subsystem here, so we do not support it. 626 return null; 627 } 628 return new Wakeup(type, Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis, 629 uptimeMillis, responsibleSubsystems); 630 } 631 632 @Override toString()633 public String toString() { 634 return "Wakeup{" 635 + "mType=" + mType 636 + ", mElapsedMillis=" + mElapsedMillis 637 + ", mUptimeMillis=" + mUptimeMillis 638 + ", mDevices=" + Arrays.toString(mDevices) 639 + ", mResponsibleSubsystems=" + mResponsibleSubsystems 640 + '}'; 641 } 642 643 static final class IrqDevice { 644 int mLine; 645 String mDevice; 646 IrqDevice(int line, String device)647 IrqDevice(int line, String device) { 648 mLine = line; 649 mDevice = device; 650 } 651 652 @Override toString()653 public String toString() { 654 return "IrqDevice{" + "mLine=" + mLine + ", mDevice=\'" + mDevice + '\'' + '}'; 655 } 656 } 657 } 658 659 static final class Config implements DeviceConfig.OnPropertiesChangedListener { 660 static final String KEY_WAKEUP_STATS_RETENTION_MS = "wakeup_stats_retention_ms"; 661 static final String KEY_WAKEUP_MATCHING_WINDOW_MS = "wakeup_matching_window_ms"; 662 static final String KEY_WAKING_ACTIVITY_RETENTION_MS = "waking_activity_retention_ms"; 663 664 private static final String[] PROPERTY_NAMES = { 665 KEY_WAKEUP_STATS_RETENTION_MS, 666 KEY_WAKEUP_MATCHING_WINDOW_MS, 667 KEY_WAKING_ACTIVITY_RETENTION_MS, 668 }; 669 670 static final long DEFAULT_WAKEUP_STATS_RETENTION_MS = TimeUnit.DAYS.toMillis(3); 671 private static final long DEFAULT_WAKEUP_MATCHING_WINDOW_MS = TimeUnit.SECONDS.toMillis(1); 672 private static final long DEFAULT_WAKING_ACTIVITY_RETENTION_MS = 673 TimeUnit.MINUTES.toMillis(5); 674 675 /** 676 * Wakeup stats are retained only for this duration. 677 */ 678 public volatile long WAKEUP_STATS_RETENTION_MS = DEFAULT_WAKEUP_STATS_RETENTION_MS; 679 public volatile long WAKEUP_MATCHING_WINDOW_MS = DEFAULT_WAKEUP_MATCHING_WINDOW_MS; 680 public volatile long WAKING_ACTIVITY_RETENTION_MS = DEFAULT_WAKING_ACTIVITY_RETENTION_MS; 681 682 @SuppressLint("MissingPermission") register(Executor executor)683 void register(Executor executor) { 684 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS, 685 executor, this); 686 onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BATTERY_STATS, 687 PROPERTY_NAMES)); 688 } 689 690 @Override onPropertiesChanged(DeviceConfig.Properties properties)691 public void onPropertiesChanged(DeviceConfig.Properties properties) { 692 for (String name : properties.getKeyset()) { 693 if (name == null) { 694 continue; 695 } 696 switch (name) { 697 case KEY_WAKEUP_STATS_RETENTION_MS: 698 WAKEUP_STATS_RETENTION_MS = properties.getLong( 699 KEY_WAKEUP_STATS_RETENTION_MS, DEFAULT_WAKEUP_STATS_RETENTION_MS); 700 break; 701 case KEY_WAKEUP_MATCHING_WINDOW_MS: 702 WAKEUP_MATCHING_WINDOW_MS = properties.getLong( 703 KEY_WAKEUP_MATCHING_WINDOW_MS, DEFAULT_WAKEUP_MATCHING_WINDOW_MS); 704 break; 705 case KEY_WAKING_ACTIVITY_RETENTION_MS: 706 WAKING_ACTIVITY_RETENTION_MS = properties.getLong( 707 KEY_WAKING_ACTIVITY_RETENTION_MS, 708 DEFAULT_WAKING_ACTIVITY_RETENTION_MS); 709 break; 710 } 711 } 712 } 713 dump(IndentingPrintWriter pw)714 void dump(IndentingPrintWriter pw) { 715 pw.println("Config:"); 716 717 pw.increaseIndent(); 718 719 pw.print(KEY_WAKEUP_STATS_RETENTION_MS); 720 pw.print("="); 721 TimeUtils.formatDuration(WAKEUP_STATS_RETENTION_MS, pw); 722 pw.println(); 723 724 pw.print(KEY_WAKEUP_MATCHING_WINDOW_MS); 725 pw.print("="); 726 TimeUtils.formatDuration(WAKEUP_MATCHING_WINDOW_MS, pw); 727 pw.println(); 728 729 pw.print(KEY_WAKING_ACTIVITY_RETENTION_MS); 730 pw.print("="); 731 TimeUtils.formatDuration(WAKING_ACTIVITY_RETENTION_MS, pw); 732 pw.println(); 733 734 pw.decreaseIndent(); 735 } 736 } 737 } 738