Migrate to PowerStats HAL 2.0

Bug: 169622556
Test: Tested and verified ODPM data is correctly captured in incident
reports.  Tests pass.  Service gracefully fails on devices without
PowerStats HAL 2.0 implementation.

Change-Id: I521e0a8fd26122476601d8c8450f0508549eb311
This commit is contained in:
Mat Bevilacqua 2020-09-21 18:59:06 -07:00
parent d681a590c7
commit a375ea0bf7
14 changed files with 919 additions and 731 deletions

View File

@ -511,9 +511,14 @@ message IncidentProto {
(section).args = "sensorservice --proto"
];
optional com.android.server.powerstats.PowerStatsServiceProto powerstats = 3054 [
optional com.android.server.powerstats.PowerStatsServiceMeterProto powerstats_meter = 3054 [
(section).type = SECTION_DUMPSYS,
(section).args = "power_stats --proto"
(section).args = "power_stats --proto meter"
];
optional com.android.server.powerstats.PowerStatsServiceModelProto powerstats_model = 3055 [
(section).type = SECTION_DUMPSYS,
(section).args = "power_stats --proto model"
];
// Dumps in text format (on userdebug and eng builds only): 4000 ~ 4999

View File

@ -20,44 +20,99 @@ package com.android.server.powerstats;
option java_multiple_files = true;
message IncidentReportProto {
/**
* IncidentReportMeterProto is used only in the parsing tool located
* in frameworks/base/tools which is used to parse this data out of
* incident reports.
*/
message IncidentReportMeterProto {
/** Section number matches that in incident.proto */
optional PowerStatsServiceProto incident_report = 3054;
}
message PowerStatsServiceProto {
repeated RailInfoProto rail_info = 1;
repeated EnergyDataProto energy_data = 2;
optional PowerStatsServiceMeterProto incident_report = 3054;
}
/**
* Rail information:
* Reports information related to the rails being monitored.
* IncidentReportModelProto is used only in the parsing tool located
* in frameworks/base/tools which is used to parse this data out of
* incident reports.
*/
message RailInfoProto {
/** Index corresponding to the rail */
optional int32 index = 1;
/** Name of the rail (opaque to the framework) */
optional string rail_name = 2;
/** Name of the subsystem to which this rail belongs (opaque to the framework) */
optional string subsys_name = 3;
/** Hardware sampling rate */
optional int32 sampling_rate = 4;
message IncidentReportModelProto {
/** Section number matches that in incident.proto */
optional PowerStatsServiceModelProto incident_report = 3055;
}
/**
* Rail level energy measurements:
* Reports accumulated energy since boot on each rail.
* EnergyConsumer (model) data is exposed by the PowerStats HAL. This data
* represents modeled energy consumption estimates and is provided per
* subsystem. The default subsystems are defined in EnergyConsumerId.aidl.
* Energy model estimates will be logged to incident reports in addition to
* the raw energy meter data.
*/
message EnergyDataProto {
/**
* Index corresponding to the rail. This index matches
* the index returned in RailInfo
*/
optional int32 index = 1;
message PowerStatsServiceModelProto {
repeated EnergyConsumerIdProto energy_consumer_id = 1;
repeated EnergyConsumerResultProto energy_consumer_result = 2;
}
/**
* EnergyMeasurement (meter) data is exposed by the PowerStats HAL. This data
* represents measurements taken directly from on-device energy meters.
* This raw energy meter data will be logged to incident reports.
*/
message PowerStatsServiceMeterProto {
repeated ChannelInfoProto channel_info = 1;
repeated EnergyMeasurementProto energy_measurement = 2;
}
/**
* Energy consumer ID:
* A list of default subsystems for which energy consumption estimates
* may be provided (hardware dependent).
*/
message EnergyConsumerIdProto {
/** Unique index identifying the energy consumer. */
optional int32 energy_consumer_id = 1;
}
/**
* Energy consumer result:
* An estimate of energy consumption since boot for the subsystem identified
* by the unique energy_consumer_id.
*/
message EnergyConsumerResultProto {
/** Unique index identifying the energy consumer. */
optional int32 energy_consumer_id = 1;
/** Time since device boot(CLOCK_BOOTTIME) in milli-seconds */
optional int64 timestamp_ms = 2;
/** Accumulated energy since device boot in microwatt-seconds (uWs) */
optional int64 energy_uws = 3;
}
/**
* Channel information:
* Reports information related to the energy meter channels being monitored.
*/
message ChannelInfoProto {
/**
* Index corresponding to the energy meter channel. This index matches
* the index returned in ChannelInfo.
*/
optional int32 channel_id = 1;
/** Name of the energy meter channel */
optional string channel_name = 2;
}
/**
* Energy measurements:
* Reports accumulated energy since boot for each energy meter.
*/
message EnergyMeasurementProto {
/**
* Index corresponding to the energy meter channel. This index matches
* the index returned in ChannelInfo.
*/
optional int32 channel_id = 1;
/** Time since device boot(CLOCK_BOOTTIME) in milli-seconds */
optional int64 timestamp_ms = 2;

View File

@ -116,6 +116,7 @@ java_library_static {
"android.hardware.contexthub-V1.0-java",
"android.hardware.rebootescrow-java",
"android.hardware.soundtrigger-V2.3-java",
"android.hardware.power.stats-java",
"android.hidl.manager-V1.2-java",
"capture_state_listener-aidl-java",
"dnsresolver_aidl_interface-java",

View File

@ -1,286 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.powerstats;
import android.util.Log;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;
import android.util.proto.WireTypeMismatchException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* PowerStatsData is a class that performs two operations:
* 1) Unpacks serialized protobuf byte arrays, as defined in powerstatsservice.proto,
* into RailInfo or EnergyData object arrays.
*
* 2) Packs RailInfo or EnergyData object arrays in protobuf byte arrays as
* defined in powerstatsservice.proto.
*
* Inside frameworks, proto source is generated with the genstream option
* and therefore the getter/setter helper functions are not available.
* The protos need to be packed/unpacked in a more manual way using
* ProtoOutputStream/ProtoInputStream.
*/
public class PowerStatsData {
private static final String TAG = PowerStatsData.class.getSimpleName();
private List<Data> mDataList;
public PowerStatsData(ProtoInputStream pis) throws IOException {
mDataList = new ArrayList<Data>();
unpackProto(pis);
}
public PowerStatsData(Data[] data) {
mDataList = new ArrayList<Data>(Arrays.asList(data));
}
private void unpackProto(ProtoInputStream pis) throws IOException {
long token;
while (true) {
try {
switch (pis.nextField()) {
case (int) PowerStatsServiceProto.RAIL_INFO:
token = pis.start(PowerStatsServiceProto.RAIL_INFO);
mDataList.add(new RailInfo(pis));
pis.end(token);
break;
case (int) PowerStatsServiceProto.ENERGY_DATA:
token = pis.start(PowerStatsServiceProto.ENERGY_DATA);
mDataList.add(new EnergyData(pis));
pis.end(token);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return;
default:
Log.e(TAG, "Unhandled field in proto: "
+ ProtoUtils.currentFieldToString(pis));
break;
}
} catch (WireTypeMismatchException wtme) {
Log.e(TAG, "Wire Type mismatch in proto: " + ProtoUtils.currentFieldToString(pis));
}
}
}
/**
* Write this object to an output stream in protobuf format.
*
* @param pos ProtoOutputStream of file where data is to be written. Data is
* written in protobuf format as defined by powerstatsservice.proto.
*/
public void toProto(ProtoOutputStream pos) {
long token;
for (Data data : mDataList) {
if (data instanceof RailInfo) {
token = pos.start(PowerStatsServiceProto.RAIL_INFO);
} else {
token = pos.start(PowerStatsServiceProto.ENERGY_DATA);
}
data.toProto(pos);
pos.end(token);
}
}
/**
* Convert mDataList to proto format and return the serialized byte array.
*
* @return byte array containing a serialized protobuf of mDataList.
*/
public byte[] getProtoBytes() {
ProtoOutputStream pos = new ProtoOutputStream();
long token;
for (Data data : mDataList) {
if (data instanceof RailInfo) {
token = pos.start(PowerStatsServiceProto.RAIL_INFO);
} else {
token = pos.start(PowerStatsServiceProto.ENERGY_DATA);
}
data.toProto(pos);
pos.end(token);
}
return pos.getBytes();
}
/**
* Print this object to logcat.
*/
public void print() {
for (Data data : mDataList) {
Log.d(TAG, data.toString());
}
}
/**
* RailInfo is a class that stores a description for an individual ODPM
* rail. It provides functionality to unpack a RailInfo object from a
* serialized protobuf byte array, and to pack a RailInfo object into
* a ProtoOutputStream.
*/
public static class RailInfo extends Data {
public String mRailName;
public String mSubSysName;
public long mSamplingRate;
public RailInfo(ProtoInputStream pis) throws IOException {
unpackProto(pis);
}
public RailInfo(long index, String railName, String subSysName, long samplingRate) {
mIndex = index;
mRailName = railName;
mSubSysName = subSysName;
mSamplingRate = samplingRate;
}
@Override
protected void unpackProto(ProtoInputStream pis) throws IOException {
while (true) {
try {
switch (pis.nextField()) {
case (int) RailInfoProto.INDEX:
mIndex = pis.readInt(RailInfoProto.INDEX);
break;
case (int) RailInfoProto.RAIL_NAME:
mRailName = pis.readString(RailInfoProto.RAIL_NAME);
break;
case (int) RailInfoProto.SUBSYS_NAME:
mSubSysName = pis.readString(RailInfoProto.SUBSYS_NAME);
break;
case (int) RailInfoProto.SAMPLING_RATE:
mSamplingRate = pis.readInt(RailInfoProto.SAMPLING_RATE);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return;
default:
Log.e(TAG, "Unhandled field in RailInfoProto: "
+ ProtoUtils.currentFieldToString(pis));
break;
}
} catch (WireTypeMismatchException wtme) {
Log.e(TAG, "Wire Type mismatch in RailInfoProto: "
+ ProtoUtils.currentFieldToString(pis));
}
}
}
@Override
public void toProto(ProtoOutputStream pos) {
pos.write(RailInfoProto.INDEX, mIndex);
pos.write(RailInfoProto.RAIL_NAME, mRailName);
pos.write(RailInfoProto.SUBSYS_NAME, mSubSysName);
pos.write(RailInfoProto.SAMPLING_RATE, mSamplingRate);
}
@Override
public String toString() {
return String.format("Index = " + mIndex
+ ", RailName = " + mRailName
+ ", SubSysName = " + mSubSysName
+ ", SamplingRate = " + mSamplingRate);
}
}
/**
* EnergyData is a class that stores an energy (uWs) data reading for an
* individual ODPM rail. It provides functionality to unpack an EnergyData
* object from a serialized protobuf byte array, and to pack an EnergyData
* object into a ProtoOutputStream.
*/
public static class EnergyData extends Data {
public long mTimestampMs;
public long mEnergyUWs;
public EnergyData(ProtoInputStream pis) throws IOException {
unpackProto(pis);
}
public EnergyData(long index, long timestampMs, long energyUWs) {
mIndex = index;
mTimestampMs = timestampMs;
mEnergyUWs = energyUWs;
}
@Override
protected void unpackProto(ProtoInputStream pis) throws IOException {
while (true) {
try {
switch (pis.nextField()) {
case (int) EnergyDataProto.INDEX:
mIndex = pis.readInt(EnergyDataProto.INDEX);
break;
case (int) EnergyDataProto.TIMESTAMP_MS:
mTimestampMs = pis.readLong(EnergyDataProto.TIMESTAMP_MS);
break;
case (int) EnergyDataProto.ENERGY_UWS:
mEnergyUWs = pis.readLong(EnergyDataProto.ENERGY_UWS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return;
default:
Log.e(TAG, "Unhandled field in EnergyDataProto: "
+ ProtoUtils.currentFieldToString(pis));
break;
}
} catch (WireTypeMismatchException wtme) {
Log.e(TAG, "Wire Type mismatch in EnergyDataProto: "
+ ProtoUtils.currentFieldToString(pis));
}
}
}
@Override
protected void toProto(ProtoOutputStream pos) {
pos.write(EnergyDataProto.INDEX, mIndex);
pos.write(EnergyDataProto.TIMESTAMP_MS, mTimestampMs);
pos.write(EnergyDataProto.ENERGY_UWS, mEnergyUWs);
}
@Override
public String toString() {
return String.format("Index = " + mIndex
+ ", Timestamp (ms) = " + mTimestampMs
+ ", Energy (uWs) = " + mEnergyUWs);
}
}
private abstract static class Data {
public long mIndex;
protected abstract void unpackProto(ProtoInputStream pis) throws IOException;
protected abstract void toProto(ProtoOutputStream pos);
}
}

View File

@ -198,19 +198,21 @@ public class PowerStatsDataStorage {
* array and written to on-device storage.
*/
public void write(byte[] data) {
mLock.lock();
if (data.length > 0) {
mLock.lock();
long currentTimeMillis = System.currentTimeMillis();
try {
DataElement dataElement = new DataElement(data);
mFileRotator.rewriteActive(new DataRewriter(dataElement.toByteArray()),
currentTimeMillis);
mFileRotator.maybeRotate(currentTimeMillis);
} catch (IOException e) {
Log.e(TAG, "Failed to write to on-device storage: " + e);
long currentTimeMillis = System.currentTimeMillis();
try {
DataElement dataElement = new DataElement(data);
mFileRotator.rewriteActive(new DataRewriter(dataElement.toByteArray()),
currentTimeMillis);
mFileRotator.maybeRotate(currentTimeMillis);
} catch (IOException e) {
Log.e(TAG, "Failed to write to on-device storage: " + e);
}
mLock.unlock();
}
mLock.unlock();
}
/**

View File

@ -16,6 +16,17 @@
package com.android.server.powerstats;
import android.hardware.power.stats.IPowerStats;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import java.util.function.Supplier;
/**
* PowerStatsHALWrapper is a wrapper class for the PowerStats HAL API calls.
*/
@ -27,30 +38,50 @@ public final class PowerStatsHALWrapper {
*/
public interface IPowerStatsHALWrapper {
/**
* Returns rail info for all available ODPM rails.
* Returns the energy consumer IDs for all available energy consumers (power models) on the
* device. Examples of subsystems for which energy consumer results (power models)
* may be available are GPS, display, wifi, etc. The default list of energy
* consumers can be found in the PowerStats HAL definition (EnergyConsumerId.aidl).
* The availability of energy consumer IDs is hardware dependent.
*
* @return array of RailInfo objects containing rail info for all
* available rails.
* @return List of EnergyConsumerIds all available energy consumers.
*/
PowerStatsData.RailInfo[] readRailInfo();
int[] getEnergyConsumerInfo();
/**
* Returns energy data for all available ODPM rails. Available rails can
* be retrieved by calling nativeGetRailInfo. Energy data and
* rail info can be linked through the index field.
* Returns the energy consumer result for all available energy consumers (power models).
* Available consumers can be retrieved by calling getEnergyConsumerInfo(). The
* subsystem corresponding to the energy consumer result is defined by the energy
* consumer ID.
*
* @return array of EnergyData objects containing energy data for all
* available rails.
* @return List of EnergyConsumerResult objects containing energy consumer results for all
* available energy consumers (power models).
*/
PowerStatsData.EnergyData[] readEnergyData();
android.hardware.power.stats.EnergyConsumerResult[] getEnergyConsumed();
/**
* Returns channel info for all available energy meters.
*
* @return List of ChannelInfo objects containing channel info for all available energy
* meters.
*/
android.hardware.power.stats.ChannelInfo[] getEnergyMeterInfo();
/**
* Returns energy measurements for all available energy meters. Available channels can be
* retrieved by calling getEnergyMeterInfo(). Energy measurements and channel info
* can be linked through the channelId field.
*
* @return List of EnergyMeasurement objects containing energy measurements for all
* available energy meters.
*/
android.hardware.power.stats.EnergyMeasurement[] readEnergyMeters();
/**
* Returns boolean indicating if connection to power stats HAL was
* established.
*
* @return true if connection to power stats HAL was correctly established
* and that energy data and rail info can be read from the interface.
* false otherwise
* @return true if connection to power stats HAL was correctly established.
*/
boolean initialize();
}
@ -61,45 +92,108 @@ public final class PowerStatsHALWrapper {
* framework and will be passed into the PowerStatsService through an injector.
*/
public static final class PowerStatsHALWrapperImpl implements IPowerStatsHALWrapper {
private static native boolean nativeInit();
private static native PowerStatsData.RailInfo[] nativeGetRailInfo();
private static native PowerStatsData.EnergyData[] nativeGetEnergyData();
private static Supplier<IPowerStats> sVintfPowerStats;
/**
* Returns rail info for all available ODPM rails.
*
* @return array of RailInfo objects containing rail info for all
* available rails.
*/
@Override
public PowerStatsData.RailInfo[] readRailInfo() {
return nativeGetRailInfo();
public int[] getEnergyConsumerInfo() {
int[] energyConsumerInfoHAL = null;
if (sVintfPowerStats != null) {
try {
energyConsumerInfoHAL = sVintfPowerStats.get().getEnergyConsumerInfo();
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get energy consumer info from PowerStats HAL");
}
}
return energyConsumerInfoHAL;
}
/**
* Returns energy data for all available ODPM rails. Available rails can
* be retrieved by calling nativeGetRailInfo. Energy data and
* rail info can be linked through the index field.
*
* @return array of EnergyData objects containing energy data for all
* available rails.
*/
@Override
public PowerStatsData.EnergyData[] readEnergyData() {
return nativeGetEnergyData();
public android.hardware.power.stats.EnergyConsumerResult[] getEnergyConsumed() {
android.hardware.power.stats.EnergyConsumerResult[] energyConsumedHAL = null;
if (sVintfPowerStats != null) {
try {
energyConsumedHAL =
sVintfPowerStats.get().getEnergyConsumed(new int[0]);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get energy consumer results from PowerStats HAL");
}
}
return energyConsumedHAL;
}
@Override
public android.hardware.power.stats.ChannelInfo[] getEnergyMeterInfo() {
android.hardware.power.stats.ChannelInfo[] energyMeterInfoHAL = null;
if (sVintfPowerStats != null) {
try {
energyMeterInfoHAL = sVintfPowerStats.get().getEnergyMeterInfo();
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get energy meter info from PowerStats HAL");
}
}
return energyMeterInfoHAL;
}
@Override
public android.hardware.power.stats.EnergyMeasurement[] readEnergyMeters() {
android.hardware.power.stats.EnergyMeasurement[] energyMeasurementHAL = null;
if (sVintfPowerStats != null) {
try {
energyMeasurementHAL =
sVintfPowerStats.get().readEnergyMeters(new int[0]);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get energy measurements from PowerStats HAL");
}
}
return energyMeasurementHAL;
}
/**
* Returns boolean indicating if connection to power stats HAL was
* established.
*
* @return true if connection to power stats HAL was correctly established
* and that energy data and rail info can be read from the interface.
* false otherwise
*/
@Override
public boolean initialize() {
return nativeInit();
Supplier<IPowerStats> service = new VintfHalCache();
if (service.get() == null) {
sVintfPowerStats = null;
return false;
} else {
sVintfPowerStats = service;
return true;
}
}
}
private static class VintfHalCache implements Supplier<IPowerStats>, IBinder.DeathRecipient {
@GuardedBy("this")
private IPowerStats mInstance = null;
@Override
public synchronized IPowerStats get() {
if (mInstance == null) {
IBinder binder = Binder.allowBlocking(ServiceManager.waitForDeclaredService(
"android.hardware.power.stats.IPowerStats/default"));
if (binder != null) {
mInstance = IPowerStats.Stub.asInterface(binder);
try {
binder.linkToDeath(this, 0);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to register DeathRecipient for " + mInstance);
}
}
}
return mInstance;
}
@Override
public synchronized void binderDied() {
mInstance = null;
}
}
}

View File

@ -17,6 +17,9 @@
package com.android.server.powerstats;
import android.content.Context;
import android.hardware.power.stats.ChannelInfo;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyMeasurement;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@ -25,6 +28,10 @@ import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
import com.android.server.powerstats.ProtoStreamUtils.ChannelInfoUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerIdUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyMeasurementUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
@ -32,18 +39,19 @@ import java.io.FileDescriptor;
import java.io.IOException;
/**
* PowerStatsLogger is responsible for logging energy data to on-device
* storage. Messages are sent to its message handler to request that energy
* data be logged, at which time it queries the PowerStats HAL and logs the
* data to on-device storage. The on-device storage is dumped to file by
* calling writeToFile with a file descriptor that points to the output file.
* PowerStatsLogger is responsible for logging model and meter energy data to on-device storage.
* Messages are sent to its message handler to request that energy data be logged, at which time it
* queries the PowerStats HAL and logs the data to on-device storage. The on-device storage is
* dumped to file by calling writeModelDataToFile or writeMeterDataToFile with a file descriptor
* that points to the output file.
*/
public final class PowerStatsLogger extends Handler {
private static final String TAG = PowerStatsLogger.class.getSimpleName();
private static final boolean DEBUG = false;
protected static final int MSG_LOG_TO_DATA_STORAGE = 0;
private final PowerStatsDataStorage mPowerStatsDataStorage;
private final PowerStatsDataStorage mPowerStatsMeterStorage;
private final PowerStatsDataStorage mPowerStatsModelStorage;
private final IPowerStatsHALWrapper mPowerStatsHALWrapper;
@Override
@ -51,31 +59,40 @@ public final class PowerStatsLogger extends Handler {
switch (msg.what) {
case MSG_LOG_TO_DATA_STORAGE:
if (DEBUG) Log.d(TAG, "Logging to data storage");
PowerStatsData energyData =
new PowerStatsData(mPowerStatsHALWrapper.readEnergyData());
mPowerStatsDataStorage.write(energyData.getProtoBytes());
// Log power meter data.
EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeters();
mPowerStatsMeterStorage.write(
EnergyMeasurementUtils.getProtoBytes(energyMeasurements));
if (DEBUG) EnergyMeasurementUtils.print(energyMeasurements);
// Log power model data.
EnergyConsumerResult[] energyConsumerResults =
mPowerStatsHALWrapper.getEnergyConsumed();
mPowerStatsModelStorage.write(
EnergyConsumerResultUtils.getProtoBytes(energyConsumerResults));
if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResults);
break;
}
}
/**
* Writes data stored in PowerStatsDataStorage to a file descriptor.
* Writes meter data stored in PowerStatsDataStorage to a file descriptor.
*
* @param fd FileDescriptor where data stored in PowerStatsDataStorage is
* written. Data is written in protobuf format as defined by
* powerstatsservice.proto.
* @param fd FileDescriptor where meter data stored in PowerStatsDataStorage is written. Data
* is written in protobuf format as defined by powerstatsservice.proto.
*/
public void writeToFile(FileDescriptor fd) {
if (DEBUG) Log.d(TAG, "Writing to file");
public void writeMeterDataToFile(FileDescriptor fd) {
if (DEBUG) Log.d(TAG, "Writing meter data to file");
final ProtoOutputStream pos = new ProtoOutputStream(fd);
try {
PowerStatsData railInfo = new PowerStatsData(mPowerStatsHALWrapper.readRailInfo());
railInfo.toProto(pos);
if (DEBUG) railInfo.print();
ChannelInfo[] channelInfo = mPowerStatsHALWrapper.getEnergyMeterInfo();
ChannelInfoUtils.packProtoMessage(channelInfo, pos);
if (DEBUG) ChannelInfoUtils.print(channelInfo);
mPowerStatsDataStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
mPowerStatsMeterStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
@Override
public void onReadDataElement(byte[] data) {
try {
@ -84,26 +101,70 @@ public final class PowerStatsLogger extends Handler {
// TODO(b/166535853): ProtoOutputStream doesn't provide a method to write
// a byte array that already contains a serialized proto, so I have to
// deserialize, then re-serialize. This is computationally inefficient.
final PowerStatsData energyData = new PowerStatsData(pis);
energyData.toProto(pos);
if (DEBUG) energyData.print();
EnergyMeasurement[] energyMeasurement =
EnergyMeasurementUtils.unpackProtoMessage(data);
EnergyMeasurementUtils.packProtoMessage(energyMeasurement, pos);
if (DEBUG) EnergyMeasurementUtils.print(energyMeasurement);
} catch (IOException e) {
Log.e(TAG, "Failed to write energy data to incident report.");
Log.e(TAG, "Failed to write energy meter data to incident report.");
}
}
});
} catch (IOException e) {
Log.e(TAG, "Failed to write rail info to incident report.");
Log.e(TAG, "Failed to write energy meter info to incident report.");
}
pos.flush();
}
public PowerStatsLogger(Context context, File dataStoragePath, String dataStorageFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
/**
* Writes model data stored in PowerStatsDataStorage to a file descriptor.
*
* @param fd FileDescriptor where model data stored in PowerStatsDataStorage is written. Data
* is written in protobuf format as defined by powerstatsservice.proto.
*/
public void writeModelDataToFile(FileDescriptor fd) {
if (DEBUG) Log.d(TAG, "Writing model data to file");
final ProtoOutputStream pos = new ProtoOutputStream(fd);
try {
int[] energyConsumerId = mPowerStatsHALWrapper.getEnergyConsumerInfo();
EnergyConsumerIdUtils.packProtoMessage(energyConsumerId, pos);
if (DEBUG) EnergyConsumerIdUtils.print(energyConsumerId);
mPowerStatsModelStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
@Override
public void onReadDataElement(byte[] data) {
try {
final ProtoInputStream pis =
new ProtoInputStream(new ByteArrayInputStream(data));
// TODO(b/166535853): ProtoOutputStream doesn't provide a method to write
// a byte array that already contains a serialized proto, so I have to
// deserialize, then re-serialize. This is computationally inefficient.
EnergyConsumerResult[] energyConsumerResult =
EnergyConsumerResultUtils.unpackProtoMessage(data);
EnergyConsumerResultUtils.packProtoMessage(energyConsumerResult, pos);
if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResult);
} catch (IOException e) {
Log.e(TAG, "Failed to write energy model data to incident report.");
}
}
});
} catch (IOException e) {
Log.e(TAG, "Failed to write energy model info to incident report.");
}
pos.flush();
}
public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename,
String modelFilename, IPowerStatsHALWrapper powerStatsHALWrapper) {
super(Looper.getMainLooper());
mPowerStatsHALWrapper = powerStatsHALWrapper;
mPowerStatsDataStorage = new PowerStatsDataStorage(context, dataStoragePath,
dataStorageFilename);
mPowerStatsMeterStorage = new PowerStatsDataStorage(context, dataStoragePath,
meterFilename);
mPowerStatsModelStorage = new PowerStatsDataStorage(context, dataStoragePath,
modelFilename);
}
}

View File

@ -43,7 +43,8 @@ public class PowerStatsService extends SystemService {
private static final boolean DEBUG = false;
private static final String DATA_STORAGE_SUBDIR = "powerstats";
private static final int DATA_STORAGE_VERSION = 0;
private static final String DATA_STORAGE_FILENAME = "log.powerstats." + DATA_STORAGE_VERSION;
private static final String METER_FILENAME = "log.powerstats.meter." + DATA_STORAGE_VERSION;
private static final String MODEL_FILENAME = "log.powerstats.model." + DATA_STORAGE_VERSION;
private final Injector mInjector;
@ -63,8 +64,12 @@ public class PowerStatsService extends SystemService {
DATA_STORAGE_SUBDIR);
}
String createDataStorageFilename() {
return DATA_STORAGE_FILENAME;
String createMeterFilename() {
return METER_FILENAME;
}
String createModelFilename() {
return MODEL_FILENAME;
}
IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() {
@ -72,9 +77,10 @@ public class PowerStatsService extends SystemService {
}
PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
String dataStorageFilename, IPowerStatsHALWrapper powerStatsHALWrapper) {
return new PowerStatsLogger(context, dataStoragePath, dataStorageFilename,
powerStatsHALWrapper);
String meterFilename, String modelFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
return new PowerStatsLogger(context, dataStoragePath, meterFilename,
modelFilename, powerStatsHALWrapper);
}
BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
@ -91,11 +97,15 @@ public class PowerStatsService extends SystemService {
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
if (args.length > 0 && "--proto".equals(args[0])) {
if (mPowerStatsLogger == null) {
Log.e(TAG, "PowerStats HAL is not initialized. No data available.");
} else {
mPowerStatsLogger.writeToFile(fd);
if (mPowerStatsLogger == null) {
Log.e(TAG, "PowerStats HAL is not initialized. No data available.");
} else {
if (args.length > 0 && "--proto".equals(args[0])) {
if ("model".equals(args[1])) {
mPowerStatsLogger.writeModelDataToFile(fd);
} else if ("meter".equals(args[1])) {
mPowerStatsLogger.writeMeterDataToFile(fd);
}
}
}
}
@ -121,8 +131,8 @@ public class PowerStatsService extends SystemService {
// Only start logger and triggers if initialization is successful.
mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext,
mInjector.createDataStoragePath(), mInjector.createDataStorageFilename(),
mPowerStatsHALWrapper);
mInjector.createDataStoragePath(), mInjector.createMeterFilename(),
mInjector.createModelFilename(), mPowerStatsHALWrapper);
mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
} else {

View File

@ -0,0 +1,273 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.powerstats;
import android.hardware.power.stats.ChannelInfo;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyMeasurement;
import android.util.Log;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;
import android.util.proto.WireTypeMismatchException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* ProtoStreamUtils provides helper functions for the PowerStats HAL objects returned from calls
* to the PowerStats HAL APIs. It provides functions to pack/unpack object arrays to/from protobuf
* format. These helper functions are required since frameworks code uses the genstream option
* when generating source code and therefore, getter/setter helper functions are not available. The
* protobufs need to be packed/unpacked in a more manual way using
* ProtoOutputStream/ProtoInputStream. It also provides print() functions for debugging purposes.
*/
public class ProtoStreamUtils {
private static final String TAG = ProtoStreamUtils.class.getSimpleName();
static class ChannelInfoUtils {
public static void packProtoMessage(ChannelInfo[] channelInfo, ProtoOutputStream pos) {
long token;
for (int i = 0; i < channelInfo.length; i++) {
token = pos.start(PowerStatsServiceMeterProto.CHANNEL_INFO);
pos.write(ChannelInfoProto.CHANNEL_ID, channelInfo[i].channelId);
pos.write(ChannelInfoProto.CHANNEL_NAME, channelInfo[i].channelName);
pos.end(token);
}
}
public static void print(ChannelInfo[] channelInfo) {
for (int i = 0; i < channelInfo.length; i++) {
Log.d(TAG, "ChannelId = " + channelInfo[i].channelId
+ ", ChannelName = " + channelInfo[i].channelName);
}
}
}
static class EnergyMeasurementUtils {
public static byte[] getProtoBytes(EnergyMeasurement[] energyMeasurement) {
ProtoOutputStream pos = new ProtoOutputStream();
packProtoMessage(energyMeasurement, pos);
return pos.getBytes();
}
public static void packProtoMessage(EnergyMeasurement[] energyMeasurement,
ProtoOutputStream pos) {
long token;
for (int i = 0; i < energyMeasurement.length; i++) {
token = pos.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT);
pos.write(EnergyMeasurementProto.CHANNEL_ID, energyMeasurement[i].channelId);
pos.write(EnergyMeasurementProto.TIMESTAMP_MS, energyMeasurement[i].timestampMs);
pos.write(EnergyMeasurementProto.ENERGY_UWS, energyMeasurement[i].energyUWs);
pos.end(token);
}
}
public static EnergyMeasurement[] unpackProtoMessage(byte[] data) throws IOException {
final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data));
List<EnergyMeasurement> energyMeasurementList = new ArrayList<EnergyMeasurement>();
long token;
while (true) {
try {
int nextField = pis.nextField();
EnergyMeasurement energyMeasurement = new EnergyMeasurement();
if (nextField == (int) PowerStatsServiceMeterProto.ENERGY_MEASUREMENT) {
token = pis.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT);
energyMeasurementList.add(unpackProtoMessage(pis));
pis.end(token);
} else if (nextField == ProtoInputStream.NO_MORE_FIELDS) {
return energyMeasurementList.toArray(
new EnergyMeasurement[energyMeasurementList.size()]);
} else {
Log.e(TAG, "Unhandled field in proto: "
+ ProtoUtils.currentFieldToString(pis));
}
} catch (WireTypeMismatchException wtme) {
Log.e(TAG, "Wire Type mismatch in proto: "
+ ProtoUtils.currentFieldToString(pis));
}
}
}
private static EnergyMeasurement unpackProtoMessage(ProtoInputStream pis)
throws IOException {
EnergyMeasurement energyMeasurement = new EnergyMeasurement();
while (true) {
try {
switch (pis.nextField()) {
case (int) EnergyMeasurementProto.CHANNEL_ID:
energyMeasurement.channelId =
pis.readInt(EnergyMeasurementProto.CHANNEL_ID);
break;
case (int) EnergyMeasurementProto.TIMESTAMP_MS:
energyMeasurement.timestampMs =
pis.readLong(EnergyMeasurementProto.TIMESTAMP_MS);
break;
case (int) EnergyMeasurementProto.ENERGY_UWS:
energyMeasurement.energyUWs =
pis.readLong(EnergyMeasurementProto.ENERGY_UWS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return energyMeasurement;
default:
Log.e(TAG, "Unhandled field in EnergyMeasurementProto: "
+ ProtoUtils.currentFieldToString(pis));
break;
}
} catch (WireTypeMismatchException wtme) {
Log.e(TAG, "Wire Type mismatch in EnergyMeasurementProto: "
+ ProtoUtils.currentFieldToString(pis));
}
}
}
public static void print(EnergyMeasurement[] energyMeasurement) {
for (int i = 0; i < energyMeasurement.length; i++) {
Log.d(TAG, "ChannelId = " + energyMeasurement[i].channelId
+ ", Timestamp (ms) = " + energyMeasurement[i].timestampMs
+ ", Energy (uWs) = " + energyMeasurement[i].energyUWs);
}
}
}
static class EnergyConsumerIdUtils {
public static void packProtoMessage(int[] energyConsumerId, ProtoOutputStream pos) {
long token;
for (int i = 0; i < energyConsumerId.length; i++) {
token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_ID);
pos.write(EnergyConsumerIdProto.ENERGY_CONSUMER_ID, energyConsumerId[i]);
pos.end(token);
}
}
public static void print(int[] energyConsumerId) {
for (int i = 0; i < energyConsumerId.length; i++) {
Log.d(TAG, "EnergyConsumerId = " + energyConsumerId[i]);
}
}
}
static class EnergyConsumerResultUtils {
public static byte[] getProtoBytes(EnergyConsumerResult[] energyConsumerResult) {
ProtoOutputStream pos = new ProtoOutputStream();
packProtoMessage(energyConsumerResult, pos);
return pos.getBytes();
}
public static void packProtoMessage(EnergyConsumerResult[] energyConsumerResult,
ProtoOutputStream pos) {
long token;
for (int i = 0; i < energyConsumerResult.length; i++) {
token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
pos.write(EnergyConsumerResultProto.ENERGY_CONSUMER_ID,
energyConsumerResult[i].energyConsumerId);
pos.write(EnergyConsumerResultProto.TIMESTAMP_MS,
energyConsumerResult[i].timestampMs);
pos.write(EnergyConsumerResultProto.ENERGY_UWS, energyConsumerResult[i].energyUWs);
pos.end(token);
}
}
public static EnergyConsumerResult[] unpackProtoMessage(byte[] data) throws IOException {
final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data));
List<EnergyConsumerResult> energyConsumerResultList =
new ArrayList<EnergyConsumerResult>();
long token;
while (true) {
try {
int nextField = pis.nextField();
EnergyConsumerResult energyConsumerResult = new EnergyConsumerResult();
if (nextField == (int) PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT) {
token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
energyConsumerResultList.add(unpackProtoMessage(pis));
pis.end(token);
} else if (nextField == ProtoInputStream.NO_MORE_FIELDS) {
return energyConsumerResultList.toArray(
new EnergyConsumerResult[energyConsumerResultList.size()]);
} else {
Log.e(TAG, "Unhandled field in proto: "
+ ProtoUtils.currentFieldToString(pis));
}
} catch (WireTypeMismatchException wtme) {
Log.e(TAG, "Wire Type mismatch in proto: "
+ ProtoUtils.currentFieldToString(pis));
}
}
}
private static EnergyConsumerResult unpackProtoMessage(ProtoInputStream pis)
throws IOException {
EnergyConsumerResult energyConsumerResult = new EnergyConsumerResult();
while (true) {
try {
switch (pis.nextField()) {
case (int) EnergyConsumerResultProto.ENERGY_CONSUMER_ID:
energyConsumerResult.energyConsumerId =
pis.readInt(EnergyConsumerResultProto.ENERGY_CONSUMER_ID);
break;
case (int) EnergyConsumerResultProto.TIMESTAMP_MS:
energyConsumerResult.timestampMs =
pis.readLong(EnergyConsumerResultProto.TIMESTAMP_MS);
break;
case (int) EnergyConsumerResultProto.ENERGY_UWS:
energyConsumerResult.energyUWs =
pis.readLong(EnergyConsumerResultProto.ENERGY_UWS);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return energyConsumerResult;
default:
Log.e(TAG, "Unhandled field in EnergyConsumerResultProto: "
+ ProtoUtils.currentFieldToString(pis));
break;
}
} catch (WireTypeMismatchException wtme) {
Log.e(TAG, "Wire Type mismatch in EnergyConsumerResultProto: "
+ ProtoUtils.currentFieldToString(pis));
}
}
}
public static void print(EnergyConsumerResult[] energyConsumerResult) {
for (int i = 0; i < energyConsumerResult.length; i++) {
Log.d(TAG, "EnergyConsumerId = " + energyConsumerResult[i].energyConsumerId
+ ", Timestamp (ms) = " + energyConsumerResult[i].timestampMs
+ ", Energy (uWs) = " + energyConsumerResult[i].energyUWs);
}
}
}
}

View File

@ -38,7 +38,6 @@ cc_library_static {
"com_android_server_locksettings_SyntheticPasswordManager.cpp",
"com_android_server_net_NetworkStatsService.cpp",
"com_android_server_power_PowerManagerService.cpp",
"com_android_server_powerstats_PowerStatsService.cpp",
"com_android_server_security_VerityUtils.cpp",
"com_android_server_SerialService.cpp",
"com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",

View File

@ -1,222 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "PowerStatsService"
#include <android/hardware/power/stats/1.0/IPowerStats.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <log/log.h>
using android::hardware::hidl_vec;
using android::hardware::Return;
using android::hardware::power::stats::V1_0::EnergyData;
using android::hardware::power::stats::V1_0::RailInfo;
using android::hardware::power::stats::V1_0::Status;
static jclass class_railInfo;
static jmethodID method_railInfoInit;
static jclass class_energyData;
static jmethodID method_energyDataInit;
namespace android {
static std::mutex gPowerStatsHalMutex;
static sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHalV1_0_ptr = nullptr;
static void deinitPowerStats() {
gPowerStatsHalV1_0_ptr = nullptr;
}
struct PowerStatsHalDeathRecipient : virtual public hardware::hidl_death_recipient {
virtual void serviceDied(uint64_t cookie,
const wp<android::hidl::base::V1_0::IBase> &who) override {
// The HAL just died. Reset all handles to HAL services.
std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
deinitPowerStats();
}
};
sp<PowerStatsHalDeathRecipient> gPowerStatsHalDeathRecipient = new PowerStatsHalDeathRecipient();
static bool connectToPowerStatsHal() {
if (gPowerStatsHalV1_0_ptr == nullptr) {
gPowerStatsHalV1_0_ptr = android::hardware::power::stats::V1_0::IPowerStats::getService();
if (gPowerStatsHalV1_0_ptr == nullptr) {
ALOGE("Unable to get power.stats HAL service.");
return false;
}
// Link death recipient to power.stats service handle
hardware::Return<bool> linked =
gPowerStatsHalV1_0_ptr->linkToDeath(gPowerStatsHalDeathRecipient, 0);
if (!linked.isOk()) {
ALOGE("Transaction error in linking to power.stats HAL death: %s",
linked.description().c_str());
deinitPowerStats();
return false;
} else if (!linked) {
ALOGW("Unable to link to power.stats HAL death notifications");
return false;
}
}
return true;
}
static bool checkResult(const Return<void> &ret, const char *function) {
if (!ret.isOk()) {
ALOGE("%s failed: requested HAL service not available. Description: %s", function,
ret.description().c_str());
if (ret.isDeadObject()) {
deinitPowerStats();
}
return false;
}
return true;
}
static jobjectArray nativeGetRailInfo(JNIEnv *env, jclass clazz) {
std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
if (!connectToPowerStatsHal()) {
ALOGE("nativeGetRailInfo failed to connect to power.stats HAL");
return nullptr;
}
hidl_vec<RailInfo> list;
Return<void> ret = gPowerStatsHalV1_0_ptr->getRailInfo([&list](auto rails, auto status) {
if (status != Status::SUCCESS) {
ALOGW("Rail information is not available");
} else {
list = std::move(rails);
}
});
if (!checkResult(ret, __func__)) {
ALOGE("getRailInfo failed");
return nullptr;
} else {
jobjectArray railInfoArray = env->NewObjectArray(list.size(), class_railInfo, nullptr);
for (int i = 0; i < list.size(); i++) {
jstring railName = env->NewStringUTF(list[i].railName.c_str());
jstring subsysName = env->NewStringUTF(list[i].subsysName.c_str());
jobject railInfo = env->NewObject(class_railInfo, method_railInfoInit, list[i].index,
railName, subsysName, list[i].samplingRate);
env->SetObjectArrayElement(railInfoArray, i, railInfo);
env->DeleteLocalRef(railName);
env->DeleteLocalRef(subsysName);
env->DeleteLocalRef(railInfo);
}
return railInfoArray;
}
}
static jobjectArray nativeGetEnergyData(JNIEnv *env, jclass clazz) {
std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
if (!connectToPowerStatsHal()) {
ALOGE("nativeGetEnergy failed to connect to power.stats HAL");
}
hidl_vec<EnergyData> list;
Return<void> ret =
gPowerStatsHalV1_0_ptr->getEnergyData({}, [&list](auto energyData, auto status) {
if (status != Status::SUCCESS) {
ALOGW("getEnergyData is not supported");
} else {
list = std::move(energyData);
}
});
if (!checkResult(ret, __func__)) {
ALOGE("getEnergyData failed");
return nullptr;
} else {
jobjectArray energyDataArray = env->NewObjectArray(list.size(), class_energyData, nullptr);
for (int i = 0; i < list.size(); i++) {
jobject energyData = env->NewObject(class_energyData, method_energyDataInit,
list[i].index, list[i].timestamp, list[i].energy);
env->SetObjectArrayElement(energyDataArray, i, energyData);
env->DeleteLocalRef(energyData);
}
return energyDataArray;
}
}
static jboolean nativeInit(JNIEnv *env, jclass clazz) {
std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
jclass temp = env->FindClass("com/android/server/powerstats/PowerStatsData$RailInfo");
if (temp == nullptr) return false;
class_railInfo = (jclass)env->NewGlobalRef(temp);
if (class_railInfo == nullptr) return false;
method_railInfoInit =
env->GetMethodID(class_railInfo, "<init>", "(JLjava/lang/String;Ljava/lang/String;J)V");
if (method_railInfoInit == nullptr) return false;
temp = env->FindClass("com/android/server/powerstats/PowerStatsData$EnergyData");
if (temp == nullptr) return false;
class_energyData = (jclass)env->NewGlobalRef(temp);
if (class_energyData == nullptr) return false;
method_energyDataInit = env->GetMethodID(class_energyData, "<init>", "(JJJ)V");
if (method_energyDataInit == nullptr) return false;
bool rv = true;
if (!connectToPowerStatsHal()) {
ALOGE("nativeInit failed to connect to power.stats HAL");
rv = false;
} else {
Return<void> ret = gPowerStatsHalV1_0_ptr->getRailInfo([&rv](auto rails, auto status) {
if (status != Status::SUCCESS) {
ALOGE("nativeInit RailInfo is unavailable");
rv = false;
}
});
ret = gPowerStatsHalV1_0_ptr->getEnergyData({}, [&rv](auto energyData, auto status) {
if (status != Status::SUCCESS) {
ALOGE("nativeInit EnergyData is unavailable");
rv = false;
}
});
}
return rv;
}
static const JNINativeMethod method_table[] = {
{"nativeInit", "()Z", (void *)nativeInit},
{"nativeGetRailInfo", "()[Lcom/android/server/powerstats/PowerStatsData$RailInfo;",
(void *)nativeGetRailInfo},
{"nativeGetEnergyData", "()[Lcom/android/server/powerstats/PowerStatsData$EnergyData;",
(void *)nativeGetEnergyData},
};
int register_android_server_PowerStatsService(JNIEnv *env) {
return jniRegisterNativeMethods(env,
"com/android/server/powerstats/"
"PowerStatsHALWrapper$PowerStatsHALWrapperImpl",
method_table, NELEM(method_table));
}
}; // namespace android

View File

@ -29,7 +29,6 @@ int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_PowerStatsService(JNIEnv* env);
int register_android_server_storage_AppFuse(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
@ -84,7 +83,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_broadcastradio_BroadcastRadioService(env);
register_android_server_broadcastradio_Tuner(vm, env);
register_android_server_PowerManagerService(env);
register_android_server_PowerStatsService(env);
register_android_server_SerialService(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);

View File

@ -20,12 +20,16 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import android.hardware.power.stats.ChannelInfo;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyMeasurement;
import androidx.test.InstrumentationRegistry;
import com.android.server.SystemService;
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
import com.android.server.powerstats.nano.PowerStatsServiceProto;
import com.android.server.powerstats.nano.PowerStatsServiceMeterProto;
import com.android.server.powerstats.nano.PowerStatsServiceModelProto;
import org.junit.Before;
import org.junit.Test;
@ -48,11 +52,12 @@ import java.util.Random;
public class PowerStatsServiceTest {
private static final String TAG = PowerStatsServiceTest.class.getSimpleName();
private static final String DATA_STORAGE_SUBDIR = "powerstatstest";
private static final String DATA_STORAGE_FILENAME = "test";
private static final String METER_FILENAME = "metertest";
private static final String MODEL_FILENAME = "modeltest";
private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto";
private static final String RAIL_NAME = "railname";
private static final String SUBSYS_NAME = "subsysname";
private static final int POWER_RAIL_COUNT = 8;
private static final String CHANNEL_NAME = "channelname";
private static final int ENERGY_METER_COUNT = 8;
private static final int ENERGY_CONSUMER_COUNT = 2;
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
private PowerStatsService mService;
@ -75,8 +80,13 @@ public class PowerStatsServiceTest {
}
@Override
String createDataStorageFilename() {
return DATA_STORAGE_FILENAME;
String createMeterFilename() {
return METER_FILENAME;
}
@Override
String createModelFilename() {
return MODEL_FILENAME;
}
@Override
@ -86,9 +96,10 @@ public class PowerStatsServiceTest {
@Override
PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
String dataStorageFilename, IPowerStatsHALWrapper powerStatsHALWrapper) {
mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, dataStorageFilename,
powerStatsHALWrapper);
String meterFilename, String modelFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, meterFilename,
modelFilename, powerStatsHALWrapper);
return mPowerStatsLogger;
}
@ -107,23 +118,48 @@ public class PowerStatsServiceTest {
public static final class TestPowerStatsHALWrapper implements IPowerStatsHALWrapper {
@Override
public PowerStatsData.RailInfo[] readRailInfo() {
PowerStatsData.RailInfo[] railInfoArray = new PowerStatsData.RailInfo[POWER_RAIL_COUNT];
for (int i = 0; i < POWER_RAIL_COUNT; i++) {
railInfoArray[i] = new PowerStatsData.RailInfo(i, RAIL_NAME + i, SUBSYS_NAME + i,
i);
public int[] getEnergyConsumerInfo() {
int[] energyConsumerInfoList = new int[ENERGY_CONSUMER_COUNT];
for (int i = 0; i < energyConsumerInfoList.length; i++) {
energyConsumerInfoList[i] = i;
}
return railInfoArray;
return energyConsumerInfoList;
}
@Override
public PowerStatsData.EnergyData[] readEnergyData() {
PowerStatsData.EnergyData[] energyDataArray =
new PowerStatsData.EnergyData[POWER_RAIL_COUNT];
for (int i = 0; i < POWER_RAIL_COUNT; i++) {
energyDataArray[i] = new PowerStatsData.EnergyData(i, i, i);
public EnergyConsumerResult[] getEnergyConsumed() {
EnergyConsumerResult[] energyConsumedList =
new EnergyConsumerResult[ENERGY_CONSUMER_COUNT];
for (int i = 0; i < energyConsumedList.length; i++) {
energyConsumedList[i] = new EnergyConsumerResult();
energyConsumedList[i].energyConsumerId = i;
energyConsumedList[i].timestampMs = i;
energyConsumedList[i].energyUWs = i;
}
return energyDataArray;
return energyConsumedList;
}
@Override
public ChannelInfo[] getEnergyMeterInfo() {
ChannelInfo[] energyMeterInfoList = new ChannelInfo[ENERGY_METER_COUNT];
for (int i = 0; i < energyMeterInfoList.length; i++) {
energyMeterInfoList[i] = new ChannelInfo();
energyMeterInfoList[i].channelId = i;
energyMeterInfoList[i].channelName = new String(CHANNEL_NAME + i);
}
return energyMeterInfoList;
}
@Override
public EnergyMeasurement[] readEnergyMeters() {
EnergyMeasurement[] energyMeasurementList = new EnergyMeasurement[ENERGY_METER_COUNT];
for (int i = 0; i < energyMeasurementList.length; i++) {
energyMeasurementList[i] = new EnergyMeasurement();
energyMeasurementList[i].channelId = i;
energyMeasurementList[i].timestampMs = i;
energyMeasurementList[i].energyUWs = i;
}
return energyMeasurementList;
}
@Override
@ -138,7 +174,7 @@ public class PowerStatsServiceTest {
}
@Test
public void testWrittenPowerStatsHALDataMatchesReadIncidentReportData()
public void testWrittenMeterDataMatchesReadIncidentReportData()
throws InterruptedException, IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@ -152,36 +188,74 @@ public class PowerStatsServiceTest {
// Write on-device storage to an incident report.
File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME);
FileOutputStream fos = new FileOutputStream(incidentReport);
mPowerStatsLogger.writeToFile(fos.getFD());
mPowerStatsLogger.writeMeterDataToFile(fos.getFD());
// Read the incident report in to a byte array.
FileInputStream fis = new FileInputStream(incidentReport);
byte[] fileContent = new byte[(int) incidentReport.length()];
fis.read(fileContent);
// Parse the incident data into a PowerStatsServiceProto object.
PowerStatsServiceProto pssProto = PowerStatsServiceProto.parseFrom(fileContent);
// Parse the incident data into a PowerStatsServiceMeterProto object.
PowerStatsServiceMeterProto pssProto = PowerStatsServiceMeterProto.parseFrom(fileContent);
// Validate the railInfo array matches what was written to on-device storage.
assertTrue(pssProto.railInfo.length == POWER_RAIL_COUNT);
for (int i = 0; i < pssProto.railInfo.length; i++) {
assertTrue(pssProto.railInfo[i].index == i);
assertTrue(pssProto.railInfo[i].railName.equals(RAIL_NAME + i));
assertTrue(pssProto.railInfo[i].subsysName.equals(SUBSYS_NAME + i));
assertTrue(pssProto.railInfo[i].samplingRate == i);
// Validate the channelInfo array matches what was written to on-device storage.
assertTrue(pssProto.channelInfo.length == ENERGY_METER_COUNT);
for (int i = 0; i < pssProto.channelInfo.length; i++) {
assertTrue(pssProto.channelInfo[i].channelId == i);
assertTrue(pssProto.channelInfo[i].channelName.equals(CHANNEL_NAME + i));
}
// Validate the energyData array matches what was written to on-device storage.
assertTrue(pssProto.energyData.length == POWER_RAIL_COUNT);
for (int i = 0; i < pssProto.energyData.length; i++) {
assertTrue(pssProto.energyData[i].index == i);
assertTrue(pssProto.energyData[i].timestampMs == i);
assertTrue(pssProto.energyData[i].energyUws == i);
// Validate the energyMeasurement array matches what was written to on-device storage.
assertTrue(pssProto.energyMeasurement.length == ENERGY_METER_COUNT);
for (int i = 0; i < pssProto.energyMeasurement.length; i++) {
assertTrue(pssProto.energyMeasurement[i].channelId == i);
assertTrue(pssProto.energyMeasurement[i].timestampMs == i);
assertTrue(pssProto.energyMeasurement[i].energyUws == i);
}
}
@Test
public void testCorruptOnDeviceStorage() throws IOException {
public void testWrittenModelDataMatchesReadIncidentReportData()
throws InterruptedException, IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Write data to on-device storage.
mTimerTrigger.logPowerStatsData();
// The above call puts a message on a handler. Wait for
// it to be processed.
Thread.sleep(100);
// Write on-device storage to an incident report.
File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME);
FileOutputStream fos = new FileOutputStream(incidentReport);
mPowerStatsLogger.writeModelDataToFile(fos.getFD());
// Read the incident report in to a byte array.
FileInputStream fis = new FileInputStream(incidentReport);
byte[] fileContent = new byte[(int) incidentReport.length()];
fis.read(fileContent);
// Parse the incident data into a PowerStatsServiceModelProto object.
PowerStatsServiceModelProto pssProto = PowerStatsServiceModelProto.parseFrom(fileContent);
// Validate the energyConsumerId array matches what was written to on-device storage.
assertTrue(pssProto.energyConsumerId.length == ENERGY_CONSUMER_COUNT);
for (int i = 0; i < pssProto.energyConsumerId.length; i++) {
assertTrue(pssProto.energyConsumerId[i].energyConsumerId == i);
}
// Validate the energyConsumerResult array matches what was written to on-device storage.
assertTrue(pssProto.energyConsumerResult.length == ENERGY_CONSUMER_COUNT);
for (int i = 0; i < pssProto.energyConsumerResult.length; i++) {
assertTrue(pssProto.energyConsumerResult[i].energyConsumerId == i);
assertTrue(pssProto.energyConsumerResult[i].timestampMs == i);
assertTrue(pssProto.energyConsumerResult[i].energyUws == i);
}
}
@Test
public void testCorruptOnDeviceMeterStorage() throws IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Generate random array of bytes to emulate corrupt data.
@ -191,7 +265,7 @@ public class PowerStatsServiceTest {
// Store corrupt data in on-device storage. Add fake timestamp to filename
// to match format expected by FileRotator.
File onDeviceStorageFile = new File(mDataStorageDir, DATA_STORAGE_FILENAME + ".1234-2234");
File onDeviceStorageFile = new File(mDataStorageDir, METER_FILENAME + ".1234-2234");
FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
@ -199,33 +273,72 @@ public class PowerStatsServiceTest {
// Write on-device storage to an incident report.
File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME);
FileOutputStream incidentReportFos = new FileOutputStream(incidentReport);
mPowerStatsLogger.writeToFile(incidentReportFos.getFD());
mPowerStatsLogger.writeMeterDataToFile(incidentReportFos.getFD());
// Read the incident report in to a byte array.
FileInputStream fis = new FileInputStream(incidentReport);
byte[] fileContent = new byte[(int) incidentReport.length()];
fis.read(fileContent);
// Parse the incident data into a PowerStatsServiceProto object.
PowerStatsServiceProto pssProto = PowerStatsServiceProto.parseFrom(fileContent);
// Parse the incident data into a PowerStatsServiceMeterProto object.
PowerStatsServiceMeterProto pssProto = PowerStatsServiceMeterProto.parseFrom(fileContent);
// Valid railInfo data is written to the incident report in the call to
// mPowerStatsLogger.writeToFile().
assertTrue(pssProto.railInfo.length == POWER_RAIL_COUNT);
for (int i = 0; i < pssProto.railInfo.length; i++) {
assertTrue(pssProto.railInfo[i].index == i);
assertTrue(pssProto.railInfo[i].railName.equals(RAIL_NAME + i));
assertTrue(pssProto.railInfo[i].subsysName.equals(SUBSYS_NAME + i));
assertTrue(pssProto.railInfo[i].samplingRate == i);
// Valid channelInfo data is written to the incident report in the call to
// mPowerStatsLogger.writeMeterDataToFile().
assertTrue(pssProto.channelInfo.length == ENERGY_METER_COUNT);
for (int i = 0; i < pssProto.channelInfo.length; i++) {
assertTrue(pssProto.channelInfo[i].channelId == i);
assertTrue(pssProto.channelInfo[i].channelName.equals(CHANNEL_NAME + i));
}
// No energyData should be written to the incident report since it
// No energyMeasurements should be written to the incident report since it
// is all corrupt (random bytes generated above).
assertTrue(pssProto.energyData.length == 0);
assertTrue(pssProto.energyMeasurement.length == 0);
}
@Test
public void testNotEnoughBytesAfterLengthField() throws IOException {
public void testCorruptOnDeviceModelStorage() throws IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Generate random array of bytes to emulate corrupt data.
Random rd = new Random();
byte[] bytes = new byte[100];
rd.nextBytes(bytes);
// Store corrupt data in on-device storage. Add fake timestamp to filename
// to match format expected by FileRotator.
File onDeviceStorageFile = new File(mDataStorageDir, MODEL_FILENAME + ".1234-2234");
FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Write on-device storage to an incident report.
File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME);
FileOutputStream incidentReportFos = new FileOutputStream(incidentReport);
mPowerStatsLogger.writeModelDataToFile(incidentReportFos.getFD());
// Read the incident report in to a byte array.
FileInputStream fis = new FileInputStream(incidentReport);
byte[] fileContent = new byte[(int) incidentReport.length()];
fis.read(fileContent);
// Parse the incident data into a PowerStatsServiceModelProto object.
PowerStatsServiceModelProto pssProto = PowerStatsServiceModelProto.parseFrom(fileContent);
// Valid energyConsumerId data is written to the incident report in the call to
// mPowerStatsLogger.writeModelDataToFile().
assertTrue(pssProto.energyConsumerId.length == ENERGY_CONSUMER_COUNT);
for (int i = 0; i < pssProto.energyConsumerId.length; i++) {
assertTrue(pssProto.energyConsumerId[i].energyConsumerId == i);
}
// No energyConsumerResults should be written to the incident report since it
// is all corrupt (random bytes generated above).
assertTrue(pssProto.energyConsumerResult.length == 0);
}
@Test
public void testNotEnoughBytesAfterMeterLengthField() throws IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Create corrupt data.
@ -236,7 +349,7 @@ public class PowerStatsServiceTest {
// Store corrupt data in on-device storage. Add fake timestamp to filename
// to match format expected by FileRotator.
File onDeviceStorageFile = new File(mDataStorageDir, DATA_STORAGE_FILENAME + ".1234-2234");
File onDeviceStorageFile = new File(mDataStorageDir, METER_FILENAME + ".1234-2234");
FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(data.toByteArray());
onDeviceStorageFos.close();
@ -244,28 +357,68 @@ public class PowerStatsServiceTest {
// Write on-device storage to an incident report.
File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME);
FileOutputStream incidentReportFos = new FileOutputStream(incidentReport);
mPowerStatsLogger.writeToFile(incidentReportFos.getFD());
mPowerStatsLogger.writeMeterDataToFile(incidentReportFos.getFD());
// Read the incident report in to a byte array.
FileInputStream fis = new FileInputStream(incidentReport);
byte[] fileContent = new byte[(int) incidentReport.length()];
fis.read(fileContent);
// Parse the incident data into a PowerStatsServiceProto object.
PowerStatsServiceProto pssProto = PowerStatsServiceProto.parseFrom(fileContent);
// Parse the incident data into a PowerStatsServiceMeterProto object.
PowerStatsServiceMeterProto pssProto = PowerStatsServiceMeterProto.parseFrom(fileContent);
// Valid railInfo data is written to the incident report in the call to
// mPowerStatsLogger.writeToFile().
assertTrue(pssProto.railInfo.length == POWER_RAIL_COUNT);
for (int i = 0; i < pssProto.railInfo.length; i++) {
assertTrue(pssProto.railInfo[i].index == i);
assertTrue(pssProto.railInfo[i].railName.equals(RAIL_NAME + i));
assertTrue(pssProto.railInfo[i].subsysName.equals(SUBSYS_NAME + i));
assertTrue(pssProto.railInfo[i].samplingRate == i);
// Valid channelInfo data is written to the incident report in the call to
// mPowerStatsLogger.writeMeterDataToFile().
assertTrue(pssProto.channelInfo.length == ENERGY_METER_COUNT);
for (int i = 0; i < pssProto.channelInfo.length; i++) {
assertTrue(pssProto.channelInfo[i].channelId == i);
assertTrue(pssProto.channelInfo[i].channelName.equals(CHANNEL_NAME + i));
}
// No energyData should be written to the incident report since the
// No energyMeasurements should be written to the incident report since the
// input buffer had only length and no data.
assertTrue(pssProto.energyData.length == 0);
assertTrue(pssProto.energyMeasurement.length == 0);
}
@Test
public void testNotEnoughBytesAfterModelLengthField() throws IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Create corrupt data.
// Length field is correct, but there is no data following the length.
ByteArrayOutputStream data = new ByteArrayOutputStream();
data.write(ByteBuffer.allocate(4).putInt(50).array());
byte[] test = data.toByteArray();
// Store corrupt data in on-device storage. Add fake timestamp to filename
// to match format expected by FileRotator.
File onDeviceStorageFile = new File(mDataStorageDir, MODEL_FILENAME + ".1234-2234");
FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(data.toByteArray());
onDeviceStorageFos.close();
// Write on-device storage to an incident report.
File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME);
FileOutputStream incidentReportFos = new FileOutputStream(incidentReport);
mPowerStatsLogger.writeModelDataToFile(incidentReportFos.getFD());
// Read the incident report in to a byte array.
FileInputStream fis = new FileInputStream(incidentReport);
byte[] fileContent = new byte[(int) incidentReport.length()];
fis.read(fileContent);
// Parse the incident data into a PowerStatsServiceModelProto object.
PowerStatsServiceModelProto pssProto = PowerStatsServiceModelProto.parseFrom(fileContent);
// Valid energyConsumerId data is written to the incident report in the call to
// mPowerStatsLogger.writeModelDataToFile().
assertTrue(pssProto.energyConsumerId.length == ENERGY_CONSUMER_COUNT);
for (int i = 0; i < pssProto.energyConsumerId.length; i++) {
assertTrue(pssProto.energyConsumerId[i].energyConsumerId == i);
}
// No energyConsumerResults should be written to the incident report since the
// input buffer had only length and no data.
assertTrue(pssProto.energyConsumerResult.length == 0);
}
}

View File

@ -25,50 +25,95 @@ import java.io.IOException;
* is output to STDOUT in csv format.
*/
public class PowerStatsServiceProtoParser {
private static void printRailInfo(PowerStatsServiceProto proto) {
private static void printEnergyMeterInfo(PowerStatsServiceMeterProto proto) {
String csvHeader = new String();
for (int i = 0; i < proto.getRailInfoCount(); i++) {
RailInfoProto railInfo = proto.getRailInfo(i);
csvHeader += "Index" + ","
+ "Timestamp" + ","
+ railInfo.getRailName() + "/" + railInfo.getSubsysName() + ",";
for (int i = 0; i < proto.getChannelInfoCount(); i++) {
ChannelInfoProto energyMeterInfo = proto.getChannelInfo(i);
csvHeader += "Index,Timestamp," + energyMeterInfo.getChannelId()
+ "/" + energyMeterInfo.getChannelName() + ",";
}
System.out.println(csvHeader);
}
private static void printEnergyData(PowerStatsServiceProto proto) {
int railInfoCount = proto.getRailInfoCount();
private static void printEnergyMeasurements(PowerStatsServiceMeterProto proto) {
int energyMeterInfoCount = proto.getChannelInfoCount();
if (railInfoCount > 0) {
int energyDataCount = proto.getEnergyDataCount();
int energyDataSetCount = energyDataCount / railInfoCount;
if (energyMeterInfoCount > 0) {
int energyMeasurementCount = proto.getEnergyMeasurementCount();
int energyMeasurementSetCount = energyMeasurementCount / energyMeterInfoCount;
for (int i = 0; i < energyDataSetCount; i++) {
for (int i = 0; i < energyMeasurementSetCount; i++) {
String csvRow = new String();
for (int j = 0; j < railInfoCount; j++) {
EnergyDataProto energyData = proto.getEnergyData(i * railInfoCount + j);
csvRow += energyData.getIndex() + ","
+ energyData.getTimestampMs() + ","
+ energyData.getEnergyUws() + ",";
for (int j = 0; j < energyMeterInfoCount; j++) {
EnergyMeasurementProto energyMeasurement =
proto.getEnergyMeasurement(i * energyMeterInfoCount + j);
csvRow += energyMeasurement.getChannelId() + ","
+ energyMeasurement.getTimestampMs() + ","
+ energyMeasurement.getEnergyUws() + ",";
}
System.out.println(csvRow);
}
} else {
System.out.println("Error: railInfoCount is zero");
System.out.println("Error: energyMeterInfoCount is zero");
}
}
private static void printEnergyConsumerId(PowerStatsServiceModelProto proto) {
String csvHeader = new String();
for (int i = 0; i < proto.getEnergyConsumerIdCount(); i++) {
EnergyConsumerIdProto energyConsumerId = proto.getEnergyConsumerId(i);
csvHeader += "Index,Timestamp," + energyConsumerId.getEnergyConsumerId() + ",";
}
System.out.println(csvHeader);
}
private static void printEnergyConsumerResults(PowerStatsServiceModelProto proto) {
int energyConsumerIdCount = proto.getEnergyConsumerIdCount();
if (energyConsumerIdCount > 0) {
int energyConsumerResultCount = proto.getEnergyConsumerResultCount();
int energyConsumerResultSetCount = energyConsumerResultCount / energyConsumerIdCount;
for (int i = 0; i < energyConsumerResultSetCount; i++) {
String csvRow = new String();
for (int j = 0; j < energyConsumerIdCount; j++) {
EnergyConsumerResultProto energyConsumerResult =
proto.getEnergyConsumerResult(i * energyConsumerIdCount + j);
csvRow += energyConsumerResult.getEnergyConsumerId() + ","
+ energyConsumerResult.getTimestampMs() + ","
+ energyConsumerResult.getEnergyUws() + ",";
}
System.out.println(csvRow);
}
} else {
System.out.println("Error: energyConsumerIdCount is zero");
}
}
private static void generateCsvFile(String pathToIncidentReport) {
try {
IncidentReportProto irProto =
IncidentReportProto.parseFrom(new FileInputStream(pathToIncidentReport));
// Print power meter data.
IncidentReportMeterProto irMeterProto =
IncidentReportMeterProto.parseFrom(new FileInputStream(pathToIncidentReport));
if (irProto.hasIncidentReport()) {
PowerStatsServiceProto pssProto = irProto.getIncidentReport();
printRailInfo(pssProto);
printEnergyData(pssProto);
if (irMeterProto.hasIncidentReport()) {
PowerStatsServiceMeterProto pssMeterProto = irMeterProto.getIncidentReport();
printEnergyMeterInfo(pssMeterProto);
printEnergyMeasurements(pssMeterProto);
} else {
System.out.println("Incident report not found. Exiting.");
System.out.println("Meter incident report not found. Exiting.");
}
// Print power model data.
IncidentReportModelProto irModelProto =
IncidentReportModelProto.parseFrom(new FileInputStream(pathToIncidentReport));
if (irModelProto.hasIncidentReport()) {
PowerStatsServiceModelProto pssModelProto = irModelProto.getIncidentReport();
printEnergyConsumerId(pssModelProto);
printEnergyConsumerResults(pssModelProto);
} else {
System.out.println("Model incident report not found. Exiting.");
}
} catch (IOException e) {
System.out.println("Unable to open incident report file: " + pathToIncidentReport);