• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.powerstats;
18 
19 import static java.lang.System.currentTimeMillis;
20 
21 import android.content.Context;
22 import android.hardware.power.stats.Channel;
23 import android.hardware.power.stats.EnergyConsumer;
24 import android.hardware.power.stats.EnergyConsumerResult;
25 import android.hardware.power.stats.EnergyMeasurement;
26 import android.hardware.power.stats.PowerEntity;
27 import android.hardware.power.stats.StateResidencyResult;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.os.SystemClock;
32 import android.util.AtomicFile;
33 import android.util.Slog;
34 import android.util.proto.ProtoInputStream;
35 import android.util.proto.ProtoOutputStream;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 
39 import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
40 import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
41 import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils;
42 import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerUtils;
43 import com.android.server.powerstats.ProtoStreamUtils.EnergyMeasurementUtils;
44 import com.android.server.powerstats.ProtoStreamUtils.PowerEntityUtils;
45 import com.android.server.powerstats.ProtoStreamUtils.StateResidencyResultUtils;
46 
47 import java.io.ByteArrayInputStream;
48 import java.io.File;
49 import java.io.FileDescriptor;
50 import java.io.FileInputStream;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.util.Arrays;
54 
55 /**
56  * PowerStatsLogger is responsible for logging model, meter, and residency data to on-device
57  * storage.  Messages are sent to its message handler to request that energy data be logged, at
58  * which time it queries the PowerStats HAL and logs the data to on-device storage.  The on-device
59  * storage is dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or
60  * writeResidencyDataToFile with a file descriptor that points to the output file.
61  */
62 public final class PowerStatsLogger extends Handler {
63     private static final String TAG = PowerStatsLogger.class.getSimpleName();
64     private static final boolean DEBUG = false;
65     protected static final int MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP = 0;
66     protected static final int MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY = 1;
67     protected static final int MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY = 2;
68 
69     // TODO(b/181240441): Add a listener to update the Wall clock baseline when changed
70     private final long mStartWallTime;
71     private final PowerStatsDataStorage mPowerStatsMeterStorage;
72     private final PowerStatsDataStorage mPowerStatsModelStorage;
73     private final PowerStatsDataStorage mPowerStatsResidencyStorage;
74     private final IPowerStatsHALWrapper mPowerStatsHALWrapper;
75     private File mDataStoragePath;
76     private boolean mDeleteMeterDataOnBoot;
77     private boolean mDeleteModelDataOnBoot;
78     private boolean mDeleteResidencyDataOnBoot;
79 
80     @Override
handleMessage(Message msg)81     public void handleMessage(Message msg) {
82         switch (msg.what) {
83             case MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY:
84                 if (DEBUG) Slog.d(TAG, "Logging to data storage on high frequency timer");
85 
86                 // Log power meter data.
87                 EnergyMeasurement[] energyMeasurements =
88                     mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
89                 EnergyMeasurementUtils.adjustTimeSinceBootToEpoch(energyMeasurements,
90                         mStartWallTime);
91                 mPowerStatsMeterStorage.write(
92                         EnergyMeasurementUtils.getProtoBytes(energyMeasurements));
93                 if (DEBUG) EnergyMeasurementUtils.print(energyMeasurements);
94 
95                 // Log power model data without attribution data.
96                 EnergyConsumerResult[] ecrNoAttribution =
97                     mPowerStatsHALWrapper.getEnergyConsumed(new int[0]);
98                 EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrNoAttribution,
99                         mStartWallTime);
100                 mPowerStatsModelStorage.write(
101                         EnergyConsumerResultUtils.getProtoBytes(ecrNoAttribution, false));
102                 if (DEBUG) EnergyConsumerResultUtils.print(ecrNoAttribution);
103                 break;
104 
105             case MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY:
106                 if (DEBUG) Slog.d(TAG, "Logging to data storage on low frequency timer");
107 
108                 // Log power model data with attribution data.
109                 EnergyConsumerResult[] ecrAttribution =
110                     mPowerStatsHALWrapper.getEnergyConsumed(new int[0]);
111                 EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrAttribution,
112                         mStartWallTime);
113                 mPowerStatsModelStorage.write(
114                         EnergyConsumerResultUtils.getProtoBytes(ecrAttribution, true));
115                 if (DEBUG) EnergyConsumerResultUtils.print(ecrAttribution);
116                 break;
117 
118             case MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP:
119                 if (DEBUG) Slog.d(TAG, "Logging to data storage on battery drop");
120 
121                 // Log state residency data.
122                 StateResidencyResult[] stateResidencyResults =
123                     mPowerStatsHALWrapper.getStateResidency(new int[0]);
124                 StateResidencyResultUtils.adjustTimeSinceBootToEpoch(stateResidencyResults,
125                         mStartWallTime);
126                 mPowerStatsResidencyStorage.write(
127                         StateResidencyResultUtils.getProtoBytes(stateResidencyResults));
128                 if (DEBUG) StateResidencyResultUtils.print(stateResidencyResults);
129                 break;
130         }
131     }
132 
133     /**
134      * Writes meter data stored in PowerStatsDataStorage to a file descriptor.
135      *
136      * @param fd FileDescriptor where meter data stored in PowerStatsDataStorage is written.  Data
137      *           is written in protobuf format as defined by powerstatsservice.proto.
138      */
writeMeterDataToFile(FileDescriptor fd)139     public void writeMeterDataToFile(FileDescriptor fd) {
140         if (DEBUG) Slog.d(TAG, "Writing meter data to file");
141 
142         final ProtoOutputStream pos = new ProtoOutputStream(fd);
143 
144         try {
145             Channel[] channel = mPowerStatsHALWrapper.getEnergyMeterInfo();
146             ChannelUtils.packProtoMessage(channel, pos);
147             if (DEBUG) ChannelUtils.print(channel);
148 
149             mPowerStatsMeterStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
150                 @Override
151                 public void onReadDataElement(byte[] data) {
152                     try {
153                         final ProtoInputStream pis =
154                                 new ProtoInputStream(new ByteArrayInputStream(data));
155                         // TODO(b/166535853): ProtoOutputStream doesn't provide a method to write
156                         // a byte array that already contains a serialized proto, so I have to
157                         // deserialize, then re-serialize.  This is computationally inefficient.
158                         EnergyMeasurement[] energyMeasurement =
159                             EnergyMeasurementUtils.unpackProtoMessage(data);
160                         EnergyMeasurementUtils.packProtoMessage(energyMeasurement, pos);
161                         if (DEBUG) EnergyMeasurementUtils.print(energyMeasurement);
162                     } catch (IOException e) {
163                         Slog.e(TAG, "Failed to write energy meter data to incident report.");
164                     }
165                 }
166             });
167         } catch (IOException e) {
168             Slog.e(TAG, "Failed to write energy meter info to incident report.");
169         }
170 
171         pos.flush();
172     }
173 
174     /**
175      * Writes model data stored in PowerStatsDataStorage to a file descriptor.
176      *
177      * @param fd FileDescriptor where model data stored in PowerStatsDataStorage is written.  Data
178      *           is written in protobuf format as defined by powerstatsservice.proto.
179      */
writeModelDataToFile(FileDescriptor fd)180     public void writeModelDataToFile(FileDescriptor fd) {
181         if (DEBUG) Slog.d(TAG, "Writing model data to file");
182 
183         final ProtoOutputStream pos = new ProtoOutputStream(fd);
184 
185         try {
186             EnergyConsumer[] energyConsumer = mPowerStatsHALWrapper.getEnergyConsumerInfo();
187             EnergyConsumerUtils.packProtoMessage(energyConsumer, pos);
188             if (DEBUG) EnergyConsumerUtils.print(energyConsumer);
189 
190             mPowerStatsModelStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
191                 @Override
192                 public void onReadDataElement(byte[] data) {
193                     try {
194                         final ProtoInputStream pis =
195                                 new ProtoInputStream(new ByteArrayInputStream(data));
196                         // TODO(b/166535853): ProtoOutputStream doesn't provide a method to write
197                         // a byte array that already contains a serialized proto, so I have to
198                         // deserialize, then re-serialize.  This is computationally inefficient.
199                         EnergyConsumerResult[] energyConsumerResult =
200                             EnergyConsumerResultUtils.unpackProtoMessage(data);
201                         EnergyConsumerResultUtils.packProtoMessage(energyConsumerResult, pos, true);
202                         if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResult);
203                     } catch (IOException e) {
204                         Slog.e(TAG, "Failed to write energy model data to incident report.");
205                     }
206                 }
207             });
208         } catch (IOException e) {
209             Slog.e(TAG, "Failed to write energy model info to incident report.");
210         }
211 
212         pos.flush();
213     }
214 
215     /**
216      * Writes residency data stored in PowerStatsDataStorage to a file descriptor.
217      *
218      * @param fd FileDescriptor where residency data stored in PowerStatsDataStorage is written.
219      *           Data is written in protobuf format as defined by powerstatsservice.proto.
220      */
writeResidencyDataToFile(FileDescriptor fd)221     public void writeResidencyDataToFile(FileDescriptor fd) {
222         if (DEBUG) Slog.d(TAG, "Writing residency data to file");
223 
224         final ProtoOutputStream pos = new ProtoOutputStream(fd);
225 
226         try {
227             PowerEntity[] powerEntity = mPowerStatsHALWrapper.getPowerEntityInfo();
228             PowerEntityUtils.packProtoMessage(powerEntity, pos);
229             if (DEBUG) PowerEntityUtils.print(powerEntity);
230 
231             mPowerStatsResidencyStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
232                 @Override
233                 public void onReadDataElement(byte[] data) {
234                     try {
235                         final ProtoInputStream pis =
236                                 new ProtoInputStream(new ByteArrayInputStream(data));
237                         // TODO(b/166535853): ProtoOutputStream doesn't provide a method to write
238                         // a byte array that already contains a serialized proto, so I have to
239                         // deserialize, then re-serialize.  This is computationally inefficient.
240                         StateResidencyResult[] stateResidencyResult =
241                             StateResidencyResultUtils.unpackProtoMessage(data);
242                         StateResidencyResultUtils.packProtoMessage(stateResidencyResult, pos);
243                         if (DEBUG) StateResidencyResultUtils.print(stateResidencyResult);
244                     } catch (IOException e) {
245                         Slog.e(TAG, "Failed to write residency data to incident report.");
246                     }
247                 }
248             });
249         } catch (IOException e) {
250             Slog.e(TAG, "Failed to write residency data to incident report.");
251         }
252 
253         pos.flush();
254     }
255 
dataChanged(String cachedFilename, byte[] dataCurrent)256     private boolean dataChanged(String cachedFilename, byte[] dataCurrent) {
257         boolean dataChanged = false;
258 
259         if (mDataStoragePath.exists() || mDataStoragePath.mkdirs()) {
260             final File cachedFile = new File(mDataStoragePath, cachedFilename);
261 
262             if (cachedFile.exists()) {
263                 // Get the byte array for the cached data.
264                 final byte[] dataCached = new byte[(int) cachedFile.length()];
265 
266                 // Get the cached data from file.
267                 try {
268                     final FileInputStream fis = new FileInputStream(cachedFile.getPath());
269                     fis.read(dataCached);
270                 } catch (IOException e) {
271                     Slog.e(TAG, "Failed to read cached data from file");
272                 }
273 
274                 // If the cached and current data are different, delete the data store.
275                 dataChanged = !Arrays.equals(dataCached, dataCurrent);
276             } else {
277                 // Either the cached file was somehow deleted, or this is the first
278                 // boot of the device and we're creating the file for the first time.
279                 // In either case, delete the log files.
280                 dataChanged = true;
281             }
282         }
283 
284         return dataChanged;
285     }
286 
updateCacheFile(String cacheFilename, byte[] data)287     private void updateCacheFile(String cacheFilename, byte[] data) {
288         try {
289             final AtomicFile atomicCachedFile =
290                     new AtomicFile(new File(mDataStoragePath, cacheFilename));
291             final FileOutputStream fos = atomicCachedFile.startWrite();
292             fos.write(data);
293             atomicCachedFile.finishWrite(fos);
294         } catch (IOException e) {
295             Slog.e(TAG, "Failed to write current data to cached file");
296         }
297     }
298 
getDeleteMeterDataOnBoot()299     public boolean getDeleteMeterDataOnBoot() {
300         return mDeleteMeterDataOnBoot;
301     }
302 
getDeleteModelDataOnBoot()303     public boolean getDeleteModelDataOnBoot() {
304         return mDeleteModelDataOnBoot;
305     }
306 
getDeleteResidencyDataOnBoot()307     public boolean getDeleteResidencyDataOnBoot() {
308         return mDeleteResidencyDataOnBoot;
309     }
310 
311     @VisibleForTesting
getStartWallTime()312     public long getStartWallTime() {
313         return mStartWallTime;
314     }
315 
PowerStatsLogger(Context context, File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper)316     public PowerStatsLogger(Context context, File dataStoragePath,
317             String meterFilename, String meterCacheFilename,
318             String modelFilename, String modelCacheFilename,
319             String residencyFilename, String residencyCacheFilename,
320             IPowerStatsHALWrapper powerStatsHALWrapper) {
321         super(Looper.getMainLooper());
322         mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime();
323         if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime);
324         mPowerStatsHALWrapper = powerStatsHALWrapper;
325         mDataStoragePath = dataStoragePath;
326 
327         mPowerStatsMeterStorage = new PowerStatsDataStorage(context, mDataStoragePath,
328             meterFilename);
329         mPowerStatsModelStorage = new PowerStatsDataStorage(context, mDataStoragePath,
330             modelFilename);
331         mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, mDataStoragePath,
332             residencyFilename);
333 
334         final Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
335         final byte[] channelBytes = ChannelUtils.getProtoBytes(channels);
336         mDeleteMeterDataOnBoot = dataChanged(meterCacheFilename, channelBytes);
337         if (mDeleteMeterDataOnBoot) {
338             mPowerStatsMeterStorage.deleteLogs();
339             updateCacheFile(meterCacheFilename, channelBytes);
340         }
341 
342         final EnergyConsumer[] energyConsumers = mPowerStatsHALWrapper.getEnergyConsumerInfo();
343         final byte[] energyConsumerBytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
344         mDeleteModelDataOnBoot = dataChanged(modelCacheFilename, energyConsumerBytes);
345         if (mDeleteModelDataOnBoot) {
346             mPowerStatsModelStorage.deleteLogs();
347             updateCacheFile(modelCacheFilename, energyConsumerBytes);
348         }
349 
350         final PowerEntity[] powerEntities = mPowerStatsHALWrapper.getPowerEntityInfo();
351         final byte[] powerEntityBytes = PowerEntityUtils.getProtoBytes(powerEntities);
352         mDeleteResidencyDataOnBoot = dataChanged(residencyCacheFilename, powerEntityBytes);
353         if (mDeleteResidencyDataOnBoot) {
354             mPowerStatsResidencyStorage.deleteLogs();
355             updateCacheFile(residencyCacheFilename, powerEntityBytes);
356         }
357     }
358 }
359