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