• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.processor;
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.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
25 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
26 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
27 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
28 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
29 import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
30 
31 import static com.google.common.truth.Truth.assertThat;
32 
33 import android.annotation.SuppressLint;
34 import android.os.BatteryConsumer;
35 import android.os.BatteryStats;
36 import android.os.PersistableBundle;
37 import android.os.Process;
38 
39 import androidx.annotation.NonNull;
40 
41 import com.android.internal.os.MonotonicClock;
42 import com.android.internal.os.PowerStats;
43 import com.android.server.power.stats.MockClock;
44 import com.android.server.power.stats.format.BinaryStatePowerStatsLayout;
45 
46 import org.junit.Test;
47 
48 import java.util.function.Supplier;
49 
50 public class BinaryStatePowerStatsProcessorTest {
51     private static final double PRECISION = 0.00001;
52     private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
53     private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
54     private static final int POWER_COMPONENT = BatteryConsumer.POWER_COMPONENT_AUDIO;
55     private static final int TEST_STATE_FLAG = 0x1;
56 
57     private final MockClock mClock = new MockClock();
58     private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
59 
60     private static class TestBinaryStatePowerStatsProcessor extends BinaryStatePowerStatsProcessor {
TestBinaryStatePowerStatsProcessor(int powerComponentId, double averagePowerMilliAmp)61         TestBinaryStatePowerStatsProcessor(int powerComponentId, double averagePowerMilliAmp) {
62             super(powerComponentId, averagePowerMilliAmp);
63         }
64 
65         @Override
getBinaryState(BatteryStats.HistoryItem item)66         protected int getBinaryState(BatteryStats.HistoryItem item) {
67             return (item.states & TEST_STATE_FLAG) != 0 ? STATE_ON : STATE_OFF;
68         }
69     }
70 
71     @SuppressLint("CheckResult")
72     @Test
powerProfileModel()73     public void powerProfileModel() {
74         BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
75 
76         PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
77                 () -> new TestBinaryStatePowerStatsProcessor(
78                         POWER_COMPONENT,  /* averagePowerMilliAmp */ 100));
79 
80         stats.noteStateChange(buildHistoryItem(0, true, APP_UID1));
81 
82         // Turn the screen off after 2.5 seconds
83         stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
84         stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
85         stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
86 
87         stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
88 
89         stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2));
90 
91         stats.finish(11000);
92 
93         // Total usage duration is 10000
94         // Total estimated power = 10000 * 100 = 1000000 mA-ms = 0.277777 mAh
95         // Screen-on  - 25%
96         // Screen-off - 75%
97         double expectedPower = 0.277778;
98         long[] deviceStats = new long[stats.getPowerStatsDescriptor().statsArrayLength];
99         stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
100         assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
101                 .isWithin(PRECISION).of(expectedPower * 0.25);
102 
103         stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
104         assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
105                 .isWithin(PRECISION).of(expectedPower * 0.75);
106 
107         // UID1 =
108         //     6000 * 100 = 600000 mA-ms = 0.166666 mAh
109         //     split between three different states
110         double expectedPower1 = 0.166666;
111         long[] uidStats = new long[stats.getPowerStatsDescriptor().uidStatsArrayLength];
112         stats.getUidStats(uidStats, APP_UID1,
113                 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
114         assertThat(statsLayout.getUidPowerEstimate(uidStats))
115                 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
116 
117         stats.getUidStats(uidStats, APP_UID1,
118                 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
119         assertThat(statsLayout.getUidPowerEstimate(uidStats))
120                 .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
121 
122         stats.getUidStats(uidStats, APP_UID1,
123                 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
124         assertThat(statsLayout.getUidPowerEstimate(uidStats))
125                 .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000);
126 
127         // UID2 =
128         //     4000 * 100 = 400000 mA-ms = 0.111111 mAh
129         //     all in the same state
130         double expectedPower2 = 0.111111;
131         stats.getUidStats(uidStats, APP_UID2,
132                 states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
133         assertThat(statsLayout.getUidPowerEstimate(uidStats))
134                 .isWithin(PRECISION).of(expectedPower2);
135 
136         if (stats.getUidStats(uidStats, APP_UID2,
137                 states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED))) {
138             assertThat(statsLayout.getUidPowerEstimate(uidStats))
139                     .isWithin(PRECISION).of(0);
140         }
141     }
142 
143     @SuppressLint("CheckResult")
144     @Test
energyConsumerModel()145     public void energyConsumerModel() {
146         BinaryStatePowerStatsLayout
147                 statsLayout = new BinaryStatePowerStatsLayout();
148         PersistableBundle extras = new PersistableBundle();
149         statsLayout.toExtras(extras);
150         PowerStats.Descriptor descriptor = new PowerStats.Descriptor(POWER_COMPONENT,
151                 statsLayout.getDeviceStatsArrayLength(), null, 0,
152                 statsLayout.getUidStatsArrayLength(), extras);
153         PowerStats powerStats = new PowerStats(descriptor);
154         powerStats.stats = new long[descriptor.statsArrayLength];
155 
156         PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
157                 () -> new TestBinaryStatePowerStatsProcessor(
158                         POWER_COMPONENT,  /* averagePowerMilliAmp */ 100));
159 
160         stats.start(0);
161 
162         // Establish a baseline
163         stats.addPowerStats(powerStats, mMonotonicClock.monotonicTime());
164 
165         stats.noteStateChange(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         stats.noteStateChange(buildHistoryItem(6000, false, APP_UID1));
173 
174         statsLayout.setConsumedEnergy(powerStats.stats, 0, 2_160_000);
175         stats.addPowerStats(powerStats, mMonotonicClock.monotonicTime());
176 
177         stats.noteStateChange(buildHistoryItem(7000, true, APP_UID2));
178 
179         mClock.realtime = 11000;
180         statsLayout.setConsumedEnergy(powerStats.stats, 0, 1_440_000);
181         stats.addPowerStats(powerStats, mMonotonicClock.monotonicTime());
182 
183         stats.finish(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         if (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 
240     @NonNull
buildHistoryItem(int elapsedRealtime, boolean stateOn, int uid)241     private BatteryStats.HistoryItem buildHistoryItem(int elapsedRealtime, boolean stateOn,
242             int uid) {
243         mClock.realtime = elapsedRealtime;
244         BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
245         historyItem.time = mMonotonicClock.monotonicTime();
246         historyItem.states = stateOn ? TEST_STATE_FLAG : 0;
247         if (stateOn) {
248             historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
249                     | BatteryStats.HistoryItem.EVENT_FLAG_START;
250         } else {
251             historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
252                     | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
253         }
254         historyItem.eventTag = historyItem.localEventTag;
255         historyItem.eventTag.uid = uid;
256         historyItem.eventTag.string = "test";
257         return historyItem;
258     }
259 
states(int... states)260     private int[] states(int... states) {
261         return states;
262     }
263 
createAggregatedPowerStats( Supplier<PowerStatsProcessor> processorSupplier)264     private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
265             Supplier<PowerStatsProcessor> processorSupplier) {
266         AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
267         config.trackPowerComponent(POWER_COMPONENT)
268                 .trackDeviceStates(STATE_POWER, STATE_SCREEN)
269                 .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
270                 .setProcessorSupplier(processorSupplier);
271 
272         PowerComponentAggregatedPowerStats powerComponentStats =
273                 new AggregatedPowerStats(config).getPowerComponentStats(POWER_COMPONENT);
274         powerComponentStats.start(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