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