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