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