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