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