From a375ea0bf7e1ed7ff40673ebffdf2214ddca6f30 Mon Sep 17 00:00:00 2001 From: Mat Bevilacqua Date: Mon, 21 Sep 2020 18:59:06 -0700 Subject: [PATCH] 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 --- core/proto/android/os/incident.proto | 9 +- .../android/server/powerstatsservice.proto | 113 +++++-- services/core/Android.bp | 1 + .../server/powerstats/PowerStatsData.java | 286 ----------------- .../powerstats/PowerStatsDataStorage.java | 24 +- .../powerstats/PowerStatsHALWrapper.java | 180 ++++++++--- .../server/powerstats/PowerStatsLogger.java | 117 +++++-- .../server/powerstats/PowerStatsService.java | 36 ++- .../server/powerstats/ProtoStreamUtils.java | 273 ++++++++++++++++ services/core/jni/Android.bp | 1 - ...id_server_powerstats_PowerStatsService.cpp | 222 ------------- services/core/jni/onload.cpp | 2 - .../powerstats/PowerStatsServiceTest.java | 291 +++++++++++++----- .../PowerStatsServiceProtoParser.java | 95 ++++-- 14 files changed, 919 insertions(+), 731 deletions(-) delete mode 100644 services/core/java/com/android/server/powerstats/PowerStatsData.java create mode 100644 services/core/java/com/android/server/powerstats/ProtoStreamUtils.java delete mode 100644 services/core/jni/com_android_server_powerstats_PowerStatsService.cpp diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 9fed1b95f6c3..fe65bda365af 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -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 diff --git a/core/proto/android/server/powerstatsservice.proto b/core/proto/android/server/powerstatsservice.proto index c80524402335..9a7ed7cbe98f 100644 --- a/core/proto/android/server/powerstatsservice.proto +++ b/core/proto/android/server/powerstatsservice.proto @@ -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; diff --git a/services/core/Android.bp b/services/core/Android.bp index 1a7f0d1ce37e..320100072066 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -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", diff --git a/services/core/java/com/android/server/powerstats/PowerStatsData.java b/services/core/java/com/android/server/powerstats/PowerStatsData.java deleted file mode 100644 index 755bd5fce45e..000000000000 --- a/services/core/java/com/android/server/powerstats/PowerStatsData.java +++ /dev/null @@ -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 mDataList; - - public PowerStatsData(ProtoInputStream pis) throws IOException { - mDataList = new ArrayList(); - unpackProto(pis); - } - - public PowerStatsData(Data[] data) { - mDataList = new ArrayList(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); - } -} diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java index 84a6fc94598e..5d8afd43a5f7 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java @@ -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(); } /** diff --git a/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java b/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java index dc996a3e2d2e..18646b9cc06c 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java @@ -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 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 service = new VintfHalCache(); + + if (service.get() == null) { + sVintfPowerStats = null; + return false; + } else { + sVintfPowerStats = service; + return true; + } + } + } + + private static class VintfHalCache implements Supplier, 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; } } } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index 71a34a4174e5..bec99bca4d46 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -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); } } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index 81883f3012e9..b89464fe4656 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -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 { diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java new file mode 100644 index 000000000000..c29c5daf34a5 --- /dev/null +++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java @@ -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 energyMeasurementList = new ArrayList(); + 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 energyConsumerResultList = + new ArrayList(); + 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); + } + } + } +} diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index d6a56ba531d0..9f83bafbed75 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -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", diff --git a/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp b/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp deleted file mode 100644 index 5eb6b7343945..000000000000 --- a/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp +++ /dev/null @@ -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 -#include -#include - -#include - -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 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 &who) override { - // The HAL just died. Reset all handles to HAL services. - std::lock_guard lock(gPowerStatsHalMutex); - deinitPowerStats(); - } -}; - -sp 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 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 &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 lock(gPowerStatsHalMutex); - - if (!connectToPowerStatsHal()) { - ALOGE("nativeGetRailInfo failed to connect to power.stats HAL"); - return nullptr; - } - - hidl_vec list; - Return 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 lock(gPowerStatsHalMutex); - - if (!connectToPowerStatsHal()) { - ALOGE("nativeGetEnergy failed to connect to power.stats HAL"); - } - - hidl_vec list; - Return 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 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, "", "(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, "", "(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 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 diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 48d524433741..0ffa5c39bacc 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -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); diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 3221a4d4f12c..59aff8d43755 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -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); } } diff --git a/tools/powerstats/PowerStatsServiceProtoParser.java b/tools/powerstats/PowerStatsServiceProtoParser.java index 8ab302a50662..76edd6341b55 100644 --- a/tools/powerstats/PowerStatsServiceProtoParser.java +++ b/tools/powerstats/PowerStatsServiceProtoParser.java @@ -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);