1 /*
2  * Copyright (C) 2020 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;
18 
19 import android.annotation.DurationMillisLong;
20 import android.app.AlarmManager;
21 import android.os.ConditionVariable;
22 import android.os.Handler;
23 import android.util.IndentingPrintWriter;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.os.Clock;
27 import com.android.internal.os.MonotonicClock;
28 
29 import java.io.PrintWriter;
30 import java.util.Calendar;
31 import java.util.concurrent.TimeUnit;
32 import java.util.function.Supplier;
33 
34 /**
35  * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in
36  * {@link PowerStatsStore}.
37  */
38 public class PowerStatsScheduler {
39     private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1);
40     private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
41 
42     private final AlarmScheduler mAlarmScheduler;
43     private boolean mEnablePeriodicPowerStatsCollection;
44     @DurationMillisLong
45     private final long mAggregatedPowerStatsSpanDuration;
46     @DurationMillisLong
47     private final long mPowerStatsAggregationPeriod;
48     private final PowerStatsStore mPowerStatsStore;
49     private final Clock mClock;
50     private final MonotonicClock mMonotonicClock;
51     private final Handler mHandler;
52     private final Runnable mPowerStatsCollector;
53     private final Supplier<Long> mEarliestAvailableBatteryHistoryTimeMs;
54     private final PowerStatsAggregator mPowerStatsAggregator;
55     private long mLastSavedSpanEndMonotonicTime;
56 
57     /**
58      * External dependency on AlarmManager.
59      */
60     public interface AlarmScheduler {
61         /**
62          * Should use AlarmManager to schedule an inexact, non-wakeup alarm.
63          */
scheduleAlarm(long triggerAtMillis, String tag, AlarmManager.OnAlarmListener onAlarmListener, Handler handler)64         void scheduleAlarm(long triggerAtMillis, String tag,
65                 AlarmManager.OnAlarmListener onAlarmListener, Handler handler);
66     }
67 
PowerStatsScheduler(Runnable powerStatsCollector, PowerStatsAggregator powerStatsAggregator, @DurationMillisLong long aggregatedPowerStatsSpanDuration, @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore, AlarmScheduler alarmScheduler, Clock clock, MonotonicClock monotonicClock, Supplier<Long> earliestAvailableBatteryHistoryTimeMs, Handler handler)68     public PowerStatsScheduler(Runnable powerStatsCollector,
69             PowerStatsAggregator powerStatsAggregator,
70             @DurationMillisLong long aggregatedPowerStatsSpanDuration,
71             @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
72             AlarmScheduler alarmScheduler, Clock clock, MonotonicClock monotonicClock,
73             Supplier<Long> earliestAvailableBatteryHistoryTimeMs, Handler handler) {
74         mPowerStatsAggregator = powerStatsAggregator;
75         mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
76         mPowerStatsAggregationPeriod = powerStatsAggregationPeriod;
77         mPowerStatsStore = powerStatsStore;
78         mAlarmScheduler = alarmScheduler;
79         mClock = clock;
80         mMonotonicClock = monotonicClock;
81         mHandler = handler;
82         mPowerStatsCollector = powerStatsCollector;
83         mEarliestAvailableBatteryHistoryTimeMs = earliestAvailableBatteryHistoryTimeMs;
84     }
85 
86     /**
87      * Kicks off the scheduling of power stats aggregation spans.
88      */
start(boolean enablePeriodicPowerStatsCollection)89     public void start(boolean enablePeriodicPowerStatsCollection) {
90         mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection;
91         if (mEnablePeriodicPowerStatsCollection) {
92             schedulePowerStatsAggregation();
93             scheduleNextPowerStatsAggregation();
94         }
95     }
96 
scheduleNextPowerStatsAggregation()97     private void scheduleNextPowerStatsAggregation() {
98         mAlarmScheduler.scheduleAlarm(mClock.elapsedRealtime() + mPowerStatsAggregationPeriod,
99                 "PowerStats",
100                 () -> {
101                     schedulePowerStatsAggregation();
102                     mHandler.post(this::scheduleNextPowerStatsAggregation);
103                 }, mHandler);
104     }
105 
106     /**
107      * Initiate an asynchronous process of aggregation of power stats.
108      */
109     @VisibleForTesting
schedulePowerStatsAggregation()110     public void schedulePowerStatsAggregation() {
111         // Catch up the power stats collectors
112         mPowerStatsCollector.run();
113         mHandler.post(this::aggregateAndStorePowerStats);
114     }
115 
aggregateAndStorePowerStats()116     private void aggregateAndStorePowerStats() {
117         long currentTimeMillis = mClock.currentTimeMillis();
118         long currentMonotonicTime = mMonotonicClock.monotonicTime();
119         long startTime = getLastSavedSpanEndMonotonicTime();
120         if (startTime < 0) {
121             startTime = mEarliestAvailableBatteryHistoryTimeMs.get();
122         }
123         long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
124                 mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
125         while (endTimeMs <= currentMonotonicTime) {
126             mPowerStatsAggregator.aggregatePowerStats(startTime, endTimeMs,
127                     stats -> {
128                         storeAggregatedPowerStats(stats);
129                         mLastSavedSpanEndMonotonicTime = stats.getStartTime() + stats.getDuration();
130                     });
131 
132             startTime = endTimeMs;
133             endTimeMs += mAggregatedPowerStatsSpanDuration;
134         }
135     }
136 
137     /**
138      * Performs a power stats aggregation pass and then dumps all stored aggregated power stats
139      * spans followed by the remainder that has not been stored yet.
140      */
aggregateAndDumpPowerStats(PrintWriter pw)141     public void aggregateAndDumpPowerStats(PrintWriter pw) {
142         if (mHandler.getLooper().isCurrentThread()) {
143             throw new IllegalStateException("Should not be executed on the bg handler thread.");
144         }
145 
146         schedulePowerStatsAggregation();
147 
148         // Wait for the aggregation process to finish storing aggregated stats spans in the store.
149         awaitCompletion();
150 
151         IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
152         mHandler.post(() -> {
153             mPowerStatsStore.dump(ipw);
154             // Aggregate the remainder of power stats and dump the results without storing them yet.
155             long powerStoreEndMonotonicTime = getLastSavedSpanEndMonotonicTime();
156             mPowerStatsAggregator.aggregatePowerStats(powerStoreEndMonotonicTime,
157                     MonotonicClock.UNDEFINED,
158                     stats -> {
159                         // Create a PowerStatsSpan for consistency of the textual output
160                         PowerStatsSpan span = PowerStatsStore.createPowerStatsSpan(stats);
161                         if (span != null) {
162                             span.dump(ipw);
163                         }
164                     });
165         });
166 
167         awaitCompletion();
168     }
169 
170     /**
171      * Align the supplied time to the wall clock, for aesthetic purposes. For example, if
172      * the schedule is configured with a 15-min interval, the captured aggregated stats will
173      * be for spans XX:00-XX:15, XX:15-XX:30, XX:30-XX:45 and XX:45-XX:60. Only the current
174      * time is used for the alignment, so if the wall clock changed during an aggregation span,
175      * or if the device was off (which stops the monotonic clock), the alignment may be
176      * temporarily broken.
177      */
178     @VisibleForTesting
alignToWallClock(long targetMonotonicTime, long interval, long currentMonotonicTime, long currentTimeMillis)179     public static long alignToWallClock(long targetMonotonicTime, long interval,
180             long currentMonotonicTime, long currentTimeMillis) {
181 
182         // Estimate the wall clock time for the requested targetMonotonicTime
183         long targetWallClockTime = currentTimeMillis + (targetMonotonicTime - currentMonotonicTime);
184 
185         if (interval >= MINUTE_IN_MILLIS && TimeUnit.HOURS.toMillis(1) % interval == 0) {
186             // If the interval is a divisor of an hour, e.g. 10 minutes, 15 minutes, etc
187 
188             // First, round up to the next whole minute
189             Calendar cal = Calendar.getInstance();
190             cal.setTimeInMillis(targetWallClockTime + MINUTE_IN_MILLIS - 1);
191             cal.set(Calendar.SECOND, 0);
192             cal.set(Calendar.MILLISECOND, 0);
193 
194             // Now set the minute to a multiple of the requested interval
195             int intervalInMinutes = (int) (interval / MINUTE_IN_MILLIS);
196             cal.set(Calendar.MINUTE,
197                     ((cal.get(Calendar.MINUTE) + intervalInMinutes - 1) / intervalInMinutes)
198                             * intervalInMinutes);
199 
200             long adjustment = cal.getTimeInMillis() - targetWallClockTime;
201             return targetMonotonicTime + adjustment;
202         } else if (interval >= HOUR_IN_MILLIS && TimeUnit.DAYS.toMillis(1) % interval == 0) {
203             // If the interval is a divisor of a day, e.g. 2h, 3h, etc
204 
205             // First, round up to the next whole hour
206             Calendar cal = Calendar.getInstance();
207             cal.setTimeInMillis(targetWallClockTime + HOUR_IN_MILLIS - 1);
208             cal.set(Calendar.MINUTE, 0);
209             cal.set(Calendar.SECOND, 0);
210             cal.set(Calendar.MILLISECOND, 0);
211 
212             // Now set the hour of day to a multiple of the requested interval
213             int intervalInHours = (int) (interval / HOUR_IN_MILLIS);
214             cal.set(Calendar.HOUR_OF_DAY,
215                     ((cal.get(Calendar.HOUR_OF_DAY) + intervalInHours - 1) / intervalInHours)
216                     * intervalInHours);
217 
218             long adjustment = cal.getTimeInMillis() - targetWallClockTime;
219             return targetMonotonicTime + adjustment;
220         }
221 
222         return targetMonotonicTime;
223     }
224 
getLastSavedSpanEndMonotonicTime()225     private long getLastSavedSpanEndMonotonicTime() {
226         if (mLastSavedSpanEndMonotonicTime != 0) {
227             return mLastSavedSpanEndMonotonicTime;
228         }
229 
230         mLastSavedSpanEndMonotonicTime = -1;
231         for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) {
232             if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
233                 for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) {
234                     long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration;
235                     if (endMonotonicTime > mLastSavedSpanEndMonotonicTime) {
236                         mLastSavedSpanEndMonotonicTime = endMonotonicTime;
237                     }
238                 }
239             }
240         }
241         return mLastSavedSpanEndMonotonicTime;
242     }
243 
storeAggregatedPowerStats(AggregatedPowerStats stats)244     private void storeAggregatedPowerStats(AggregatedPowerStats stats) {
245         mPowerStatsStore.storeAggregatedPowerStats(stats);
246     }
247 
awaitCompletion()248     private void awaitCompletion() {
249         ConditionVariable done = new ConditionVariable();
250         mHandler.post(done::open);
251         done.block();
252     }
253 }
254