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