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