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 android.os.PersistableBundle;
20 import android.util.Slog;
21 
22 import com.android.internal.os.PowerStats;
23 
24 /**
25  * Captures the positions and lengths of sections of the stats array, such as usage duration,
26  * power usage estimates etc.
27  */
28 public class PowerStatsLayout {
29     private static final String TAG = "PowerStatsLayout";
30     private static final String EXTRA_DEVICE_POWER_POSITION = "dp";
31     private static final String EXTRA_DEVICE_DURATION_POSITION = "dd";
32     private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de";
33     private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
34     private static final String EXTRA_UID_DURATION_POSITION = "ud";
35     private static final String EXTRA_UID_POWER_POSITION = "up";
36 
37     protected static final int UNSUPPORTED = -1;
38     protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
39     protected static final int FLAG_OPTIONAL = 1;
40     protected static final int FLAG_HIDDEN = 2;
41     protected static final int FLAG_FORMAT_AS_POWER = 4;
42 
43     private int mDeviceStatsArrayLength;
44     private int mStateStatsArrayLength;
45     private int mUidStatsArrayLength;
46 
47     private StringBuilder mDeviceFormat = new StringBuilder();
48     private StringBuilder mStateFormat = new StringBuilder();
49     private StringBuilder mUidFormat = new StringBuilder();
50 
51     protected int mDeviceDurationPosition = UNSUPPORTED;
52     private int mDeviceEnergyConsumerPosition;
53     private int mDeviceEnergyConsumerCount;
54     private int mDevicePowerEstimatePosition = UNSUPPORTED;
55     private int mUidDurationPosition = UNSUPPORTED;
56     private int mUidPowerEstimatePosition = UNSUPPORTED;
57 
PowerStatsLayout()58     public PowerStatsLayout() {
59     }
60 
PowerStatsLayout(PowerStats.Descriptor descriptor)61     public PowerStatsLayout(PowerStats.Descriptor descriptor) {
62         fromExtras(descriptor.extras);
63     }
64 
getDeviceStatsArrayLength()65     public int getDeviceStatsArrayLength() {
66         return mDeviceStatsArrayLength;
67     }
68 
getStateStatsArrayLength()69     public int getStateStatsArrayLength() {
70         return mStateStatsArrayLength;
71     }
72 
getUidStatsArrayLength()73     public int getUidStatsArrayLength() {
74         return mUidStatsArrayLength;
75     }
76 
77     /**
78      * @param label should not contain either spaces or colons
79      */
appendFormat(StringBuilder sb, int position, int length, String label, int flags)80     private void appendFormat(StringBuilder sb, int position, int length, String label,
81             int flags) {
82         if ((flags & FLAG_HIDDEN) != 0) {
83             return;
84         }
85 
86         if (!sb.isEmpty()) {
87             sb.append(' ');
88         }
89 
90         sb.append(label).append(':');
91         sb.append(position);
92         if (length != 1) {
93             sb.append('[').append(length).append(']');
94         }
95         if ((flags & FLAG_FORMAT_AS_POWER) != 0) {
96             sb.append('p');
97         }
98         if ((flags & FLAG_OPTIONAL) != 0) {
99             sb.append('?');
100         }
101     }
102 
addDeviceSection(int length, String label, int flags)103     protected int addDeviceSection(int length, String label, int flags) {
104         int position = mDeviceStatsArrayLength;
105         mDeviceStatsArrayLength += length;
106         appendFormat(mDeviceFormat, position, length, label, flags);
107         return position;
108     }
109 
addDeviceSection(int length, String label)110     protected int addDeviceSection(int length, String label) {
111         return addDeviceSection(length, label, 0);
112     }
113 
addStateSection(int length, String label, int flags)114     protected int addStateSection(int length, String label, int flags) {
115         int position = mStateStatsArrayLength;
116         mStateStatsArrayLength += length;
117         appendFormat(mStateFormat, position, length, label, flags);
118         return position;
119     }
120 
addStateSection(int length, String label)121     protected int addStateSection(int length, String label) {
122         return addStateSection(length, label, 0);
123     }
124 
125 
addUidSection(int length, String label, int flags)126     protected int addUidSection(int length, String label, int flags) {
127         int position = mUidStatsArrayLength;
128         mUidStatsArrayLength += length;
129         appendFormat(mUidFormat, position, length, label, flags);
130         return position;
131     }
132 
addUidSection(int length, String label)133     protected int addUidSection(int length, String label) {
134         return addUidSection(length, label, 0);
135     }
136 
137     /**
138      * Declare that the stats array has a section capturing usage duration
139      */
addDeviceSectionUsageDuration()140     public void addDeviceSectionUsageDuration() {
141         mDeviceDurationPosition = addDeviceSection(1, "usage", FLAG_OPTIONAL);
142     }
143 
144     /**
145      * Saves the usage duration in the corresponding <code>stats</code> element.
146      */
setUsageDuration(long[] stats, long value)147     public void setUsageDuration(long[] stats, long value) {
148         stats[mDeviceDurationPosition] = value;
149     }
150 
151     /**
152      * Extracts the usage duration from the corresponding <code>stats</code> element.
153      */
getUsageDuration(long[] stats)154     public long getUsageDuration(long[] stats) {
155         return stats[mDeviceDurationPosition];
156     }
157 
158     /**
159      * Declares that the stats array has a section capturing EnergyConsumer data from
160      * PowerStatsService.
161      */
addDeviceSectionEnergyConsumers(int energyConsumerCount)162     public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
163         mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy",
164                 FLAG_OPTIONAL);
165         mDeviceEnergyConsumerCount = energyConsumerCount;
166     }
167 
getEnergyConsumerCount()168     public int getEnergyConsumerCount() {
169         return mDeviceEnergyConsumerCount;
170     }
171 
172     /**
173      * Saves the accumulated energy for the specified rail the corresponding
174      * <code>stats</code> element.
175      */
setConsumedEnergy(long[] stats, int index, long energy)176     public void setConsumedEnergy(long[] stats, int index, long energy) {
177         stats[mDeviceEnergyConsumerPosition + index] = energy;
178     }
179 
180     /**
181      * Extracts the EnergyConsumer data from a device stats array for the specified
182      * EnergyConsumer.
183      */
getConsumedEnergy(long[] stats, int index)184     public long getConsumedEnergy(long[] stats, int index) {
185         return stats[mDeviceEnergyConsumerPosition + index];
186     }
187 
188     /**
189      * Declare that the stats array has a section capturing a power estimate
190      */
addDeviceSectionPowerEstimate()191     public void addDeviceSectionPowerEstimate() {
192         mDevicePowerEstimatePosition = addDeviceSection(1, "power",
193                 FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
194     }
195 
196     /**
197      * Converts the supplied mAh power estimate to a long and saves it in the corresponding
198      * element of <code>stats</code>.
199      */
setDevicePowerEstimate(long[] stats, double power)200     public void setDevicePowerEstimate(long[] stats, double power) {
201         stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
202     }
203 
204     /**
205      * Extracts the power estimate from a device stats array and converts it to mAh.
206      */
getDevicePowerEstimate(long[] stats)207     public double getDevicePowerEstimate(long[] stats) {
208         return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
209     }
210 
211     /**
212      * Declare that the UID stats array has a section capturing usage duration
213      */
addUidSectionUsageDuration()214     public void addUidSectionUsageDuration() {
215         mUidDurationPosition = addUidSection(1, "time");
216     }
217 
218     /**
219      * Declare that the UID stats array has a section capturing a power estimate
220      */
addUidSectionPowerEstimate()221     public void addUidSectionPowerEstimate() {
222         mUidPowerEstimatePosition = addUidSection(1, "power", FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
223     }
224 
225     /**
226      * Returns true if power for this component is attributed to UIDs (apps).
227      */
isUidPowerAttributionSupported()228     public boolean isUidPowerAttributionSupported() {
229         return mUidPowerEstimatePosition != UNSUPPORTED;
230     }
231 
232     /**
233      * Saves usage duration it in the corresponding element of <code>stats</code>.
234      */
setUidUsageDuration(long[] stats, long durationMs)235     public void setUidUsageDuration(long[] stats, long durationMs) {
236         stats[mUidDurationPosition] = durationMs;
237     }
238 
239     /**
240      * Extracts the usage duration from a UID stats array.
241      */
getUidUsageDuration(long[] stats)242     public long getUidUsageDuration(long[] stats) {
243         return stats[mUidDurationPosition];
244     }
245 
246     /**
247      * Converts the supplied mAh power estimate to a long and saves it in the corresponding
248      * element of <code>stats</code>.
249      */
setUidPowerEstimate(long[] stats, double power)250     public void setUidPowerEstimate(long[] stats, double power) {
251         stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
252     }
253 
254     /**
255      * Extracts the power estimate from a UID stats array and converts it to mAh.
256      */
getUidPowerEstimate(long[] stats)257     public double getUidPowerEstimate(long[] stats) {
258         return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
259     }
260 
261     /**
262      * Copies the elements of the stats array layout into <code>extras</code>
263      */
toExtras(PersistableBundle extras)264     public void toExtras(PersistableBundle extras) {
265         extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition);
266         extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION,
267                 mDeviceEnergyConsumerPosition);
268         extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT,
269                 mDeviceEnergyConsumerCount);
270         extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
271         extras.putInt(EXTRA_UID_DURATION_POSITION, mUidDurationPosition);
272         extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
273         extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString());
274         extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString());
275         extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, mUidFormat.toString());
276     }
277 
278     /**
279      * Retrieves elements of the stats array layout from <code>extras</code>
280      */
fromExtras(PersistableBundle extras)281     public void fromExtras(PersistableBundle extras) {
282         mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION);
283         mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
284         mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
285         mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
286         mUidDurationPosition = extras.getInt(EXTRA_UID_DURATION_POSITION);
287         mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
288     }
289 
putIntArray(PersistableBundle extras, String key, int[] array)290     protected void putIntArray(PersistableBundle extras, String key, int[] array) {
291         if (array == null) {
292             return;
293         }
294 
295         StringBuilder sb = new StringBuilder();
296         for (int value : array) {
297             if (!sb.isEmpty()) {
298                 sb.append(',');
299             }
300             sb.append(value);
301         }
302         extras.putString(key, sb.toString());
303     }
304 
getIntArray(PersistableBundle extras, String key)305     protected int[] getIntArray(PersistableBundle extras, String key) {
306         String string = extras.getString(key);
307         if (string == null) {
308             return null;
309         }
310         String[] values = string.trim().split(",");
311         int[] result = new int[values.length];
312         for (int i = 0; i < values.length; i++) {
313             try {
314                 result[i] = Integer.parseInt(values[i]);
315             } catch (NumberFormatException e) {
316                 Slog.wtf(TAG, "Invalid CSV format: " + string);
317                 return null;
318             }
319         }
320         return result;
321     }
322 }
323