1 /*
2  * Copyright (C) 2024 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 static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
20 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
21 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
22 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
23 
24 import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
25 import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
26 import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
27 import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
28 import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
29 import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
30 
31 import static com.google.common.truth.Truth.assertThat;
32 
33 import android.os.BatteryConsumer;
34 import android.os.BatteryStats;
35 import android.os.PersistableBundle;
36 import android.os.Process;
37 import android.platform.test.ravenwood.RavenwoodRule;
38 
39 import androidx.annotation.NonNull;
40 
41 import com.android.internal.os.MonotonicClock;
42 import com.android.internal.os.PowerStats;
43 
44 import org.junit.Rule;
45 import org.junit.Test;
46 
47 public class BinaryStatePowerStatsProcessorTest {
48     @Rule(order = 0)
49     public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
50             .setProvideMainThread(true)
51             .build();
52 
53     private static final double PRECISION = 0.00001;
54     private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
55     private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
56     private static final int POWER_COMPONENT = BatteryConsumer.POWER_COMPONENT_AUDIO;
57     private static final int TEST_STATE_FLAG = 0x1;
58 
59     private final MockClock mClock = new MockClock();
60     private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
61     private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
62 
63     private static class TestBinaryStatePowerStatsProcessor extends BinaryStatePowerStatsProcessor {
TestBinaryStatePowerStatsProcessor(int powerComponentId, double averagePowerMilliAmp, PowerStatsUidResolver uidResolver)64         TestBinaryStatePowerStatsProcessor(int powerComponentId,
65                 double averagePowerMilliAmp, PowerStatsUidResolver uidResolver) {
66             super(powerComponentId, uidResolver, averagePowerMilliAmp);
67         }
68 
69         @Override
getBinaryState(BatteryStats.HistoryItem item)70         protected int getBinaryState(BatteryStats.HistoryItem item) {
71             return (item.states & TEST_STATE_FLAG) != 0 ? STATE_ON : STATE_OFF;
72         }
73     }
74 
75     @Test
powerProfileModel()76     public void powerProfileModel() {
77         TestBinaryStatePowerStatsProcessor processor = new TestBinaryStatePowerStatsProcessor(
78                 POWER_COMPONENT,  /* averagePowerMilliAmp */ 100, mUidResolver);
79 
80         BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
81 
82         PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
83 
84         processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
85 
86         // Turn the screen off after 2.5 seconds
87         stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
88         stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
89         stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
90 
91         processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
92 
93         processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
94 
95         processor.finish(stats, 11000);
96 
97         // Total usage duration is 10000
98         // Total estimated power = 10000 * 100 = 1000000 mA-ms = 0.277777 mAh
99         // Screen-on  - 25%
100         // Screen-off - 75%
101         double expectedPower = 0.277778;
102         long[] deviceStats = new long[stats.getPowerStatsDescriptor().statsArrayLength];
103         stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
104         assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
105                 .isWithin(PRECISION).of(expectedPower * 0.25);
106 
107         stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
108         assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
109                 .isWithin(PRECISION).of(expectedPower * 0.75);
110 
111         // UID1 =
112         //     6000 * 100 = 600000 mA-ms = 0.166666 mAh
113         //     split between three different states
114         double expectedPower1 = 0.166666;
115         long[] uidStats = new long[stats.getPowerStatsDescriptor().uidStatsArrayLength];
116         stats.getUidStats(uidStats, APP_UID1,
117                 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
118         assertThat(statsLayout.getUidPowerEstimate(uidStats))
119                 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
120 
121         stats.getUidStats(uidStats, APP_UID1,
122                 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
123         assertThat(statsLayout.getUidPowerEstimate(uidStats))
124                 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
125 
126         stats.getUidStats(uidStats, APP_UID1,
127                 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
128         assertThat(statsLayout.getUidPowerEstimate(uidStats))
129                 .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000);
130 
131         // UID2 =
132         //     4000 * 100 = 400000 mA-ms = 0.111111 mAh
133         //     all in the same state
134         double expectedPower2 = 0.111111;
135         stats.getUidStats(uidStats, APP_UID2,
136                 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
137         assertThat(statsLayout.getUidPowerEstimate(uidStats))
138                 .isWithin(PRECISION).of(expectedPower2);
139 
140         stats.getUidStats(uidStats, APP_UID2,
141                 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
142         assertThat(statsLayout.getUidPowerEstimate(uidStats))
143                 .isWithin(PRECISION).of(0);
144     }
145 
146     @Test
energyConsumerModel()147     public void energyConsumerModel() {
148         TestBinaryStatePowerStatsProcessor processor = new TestBinaryStatePowerStatsProcessor(
149                 POWER_COMPONENT,  /* averagePowerMilliAmp */ 100, mUidResolver);
150 
151         BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
152         PersistableBundle extras = new PersistableBundle();
153         statsLayout.toExtras(extras);
154         PowerStats.Descriptor descriptor = new PowerStats.Descriptor(POWER_COMPONENT,
155                 statsLayout.getDeviceStatsArrayLength(), null, 0,
156                 statsLayout.getUidStatsArrayLength(), extras);
157         PowerStats powerStats = new PowerStats(descriptor);
158         powerStats.stats = new long[descriptor.statsArrayLength];
159 
160         PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
161 
162         // Establish a baseline
163         processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
164 
165         processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
166 
167         // Turn the screen off after 2.5 seconds
168         stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
169         stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
170         stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
171 
172         processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
173 
174         statsLayout.setConsumedEnergy(powerStats.stats, 0, 2_160_000);
175         processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
176 
177         processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
178 
179         mClock.realtime = 11000;
180         statsLayout.setConsumedEnergy(powerStats.stats, 0, 1_440_000);
181         processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
182 
183         processor.finish(stats, 11000);
184 
185         // Total estimated power = 3,600,000 uC = 1.0 mAh
186         // of which 3,000,000 is distributed:
187         //     Screen-on  - 2500/6000 * 2160000 = 900000 uC = 0.25 mAh
188         //     Screen-off - 3500/6000 * 2160000 = 1260000 uC = 0.35 mAh
189         // and 600,000 was fully with screen off:
190         //     Screen-off - 1440000 uC = 0.4 mAh
191         long[] deviceStats = new long[descriptor.statsArrayLength];
192         stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
193         assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
194                 .isWithin(PRECISION).of(0.25);
195 
196         stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
197         assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
198                 .isWithin(PRECISION).of(0.35 + 0.4);
199 
200         // UID1 =
201         //     2,160,000 uC = 0.6 mAh
202         //     split between three different states
203         //          fg screen-on: 2500/6000
204         //          bg screen-off: 2500/6000
205         //          fgs screen-off: 1000/6000
206         double expectedPower1 = 0.6;
207         long[] uidStats = new long[descriptor.uidStatsArrayLength];
208         stats.getUidStats(uidStats, APP_UID1,
209                 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
210         assertThat(statsLayout.getUidPowerEstimate(uidStats))
211                 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
212 
213         stats.getUidStats(uidStats, APP_UID1,
214                 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
215         assertThat(statsLayout.getUidPowerEstimate(uidStats))
216                 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
217 
218         stats.getUidStats(uidStats, APP_UID1,
219                 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
220         assertThat(statsLayout.getUidPowerEstimate(uidStats))
221                 .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000);
222 
223         // UID2 =
224         //     1440000 mA-ms = 0.4 mAh
225         //     all in the same state
226         double expectedPower2 = 0.4;
227         stats.getUidStats(uidStats, APP_UID2,
228                 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
229         assertThat(statsLayout.getUidPowerEstimate(uidStats))
230                 .isWithin(PRECISION).of(expectedPower2);
231 
232         stats.getUidStats(uidStats, APP_UID2,
233                 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
234         assertThat(statsLayout.getUidPowerEstimate(uidStats))
235                 .isWithin(PRECISION).of(0);
236     }
237 
238 
239     @NonNull
buildHistoryItem(int elapsedRealtime, boolean stateOn, int uid)240     private BatteryStats.HistoryItem buildHistoryItem(int elapsedRealtime, boolean stateOn,
241             int uid) {
242         mClock.realtime = elapsedRealtime;
243         BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
244         historyItem.time = mMonotonicClock.monotonicTime();
245         historyItem.states = stateOn ? TEST_STATE_FLAG : 0;
246         if (stateOn) {
247             historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
248                     | BatteryStats.HistoryItem.EVENT_FLAG_START;
249         } else {
250             historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
251                     | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
252         }
253         historyItem.eventTag = historyItem.localEventTag;
254         historyItem.eventTag.uid = uid;
255         historyItem.eventTag.string = "test";
256         return historyItem;
257     }
258 
states(int... states)259     private int[] states(int... states) {
260         return states;
261     }
262 
createAggregatedPowerStats( BinaryStatePowerStatsProcessor processor)263     private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
264             BinaryStatePowerStatsProcessor processor) {
265         AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
266         config.trackPowerComponent(POWER_COMPONENT)
267                 .trackDeviceStates(STATE_POWER, STATE_SCREEN)
268                 .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
269                 .setProcessor(processor);
270 
271         AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
272         PowerComponentAggregatedPowerStats powerComponentStats =
273                 aggregatedPowerStats.getPowerComponentStats(POWER_COMPONENT);
274         processor.start(powerComponentStats, 0);
275 
276         powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
277         powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
278         powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
279         powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
280 
281         return powerComponentStats;
282     }
283 }
284