• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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