Initial commit of PowerStatsService
Bug: 164465661 Bug: 164466995 Bug: 167280723 Test: Verified PowerStatsService is functional on targets that have ODPM data. Change-Id: Ic555b380c566ea26bc2214374f142c5448ea2ee7
This commit is contained in:
parent
969e194325
commit
8160121220
@ -3384,6 +3384,7 @@ public abstract class Context {
|
||||
/** @hide */
|
||||
@StringDef(suffix = { "_SERVICE" }, value = {
|
||||
POWER_SERVICE,
|
||||
//@hide: POWER_STATS_SERVICE,
|
||||
WINDOW_SERVICE,
|
||||
LAYOUT_INFLATER_SERVICE,
|
||||
ACCOUNT_SERVICE,
|
||||
@ -3724,6 +3725,16 @@ public abstract class Context {
|
||||
*/
|
||||
public static final String POWER_SERVICE = "power";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to retrieve a
|
||||
* {@link android.os.PowerStatsService} for accessing power stats
|
||||
* service.
|
||||
*
|
||||
* @see #getSystemService(String)
|
||||
* @hide
|
||||
*/
|
||||
public static final String POWER_STATS_SERVICE = "power_stats";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to retrieve a
|
||||
* {@link android.os.RecoverySystem} for accessing the recovery system
|
||||
|
@ -40,6 +40,7 @@ import "frameworks/base/core/proto/android/server/fingerprint.proto";
|
||||
import "frameworks/base/core/proto/android/server/jobscheduler.proto";
|
||||
import "frameworks/base/core/proto/android/server/location/context_hub.proto";
|
||||
import "frameworks/base/core/proto/android/server/powermanagerservice.proto";
|
||||
import "frameworks/base/core/proto/android/server/powerstatsservice.proto";
|
||||
import "frameworks/base/core/proto/android/server/rolemanagerservice.proto";
|
||||
import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
|
||||
import "frameworks/base/core/proto/android/service/appwidget.proto";
|
||||
@ -510,6 +511,11 @@ message IncidentProto {
|
||||
(section).args = "sensorservice --proto"
|
||||
];
|
||||
|
||||
optional com.android.server.powerstats.PowerStatsServiceProto powerstats = 3054 [
|
||||
(section).type = SECTION_DUMPSYS,
|
||||
(section).args = "power_stats --proto"
|
||||
];
|
||||
|
||||
// Dumps in text format (on userdebug and eng builds only): 4000 ~ 4999
|
||||
optional android.util.TextDumpProto textdump_wifi = 4000 [
|
||||
(section).type = SECTION_TEXT_DUMPSYS,
|
||||
|
67
core/proto/android/server/powerstatsservice.proto
Normal file
67
core/proto/android/server/powerstatsservice.proto
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package com.android.server.powerstats;
|
||||
|
||||
option java_multiple_files = true;
|
||||
|
||||
message IncidentReportProto {
|
||||
/** Section number matches that in incident.proto */
|
||||
optional PowerStatsServiceProto incident_report = 3054;
|
||||
}
|
||||
|
||||
message PowerStatsServiceProto {
|
||||
repeated RailInfoProto rail_info = 1;
|
||||
repeated EnergyDataProto energy_data = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rail information:
|
||||
* Reports information related to the rails being monitored.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rail level energy measurements:
|
||||
* Reports accumulated energy since boot on each rail.
|
||||
*/
|
||||
message EnergyDataProto {
|
||||
/**
|
||||
* Index corresponding to the rail. This index matches
|
||||
* the index returned in RailInfo
|
||||
*/
|
||||
optional int32 index = 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;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.BatteryManager;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* BatteryTrigger instantiates a BroadcastReceiver that listens for changes
|
||||
* to the battery. When the battery level drops by 1% a message is sent to
|
||||
* the PowerStatsLogger to log the rail energy data to on-device storage.
|
||||
*/
|
||||
public final class BatteryTrigger extends PowerStatsLogTrigger {
|
||||
private static final String TAG = BatteryTrigger.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private int mBatteryLevel = 0;
|
||||
|
||||
private final BroadcastReceiver mBatteryLevelReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case Intent.ACTION_BATTERY_CHANGED:
|
||||
int newBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
|
||||
|
||||
if (newBatteryLevel < mBatteryLevel) {
|
||||
if (DEBUG) Log.d(TAG, "Battery level dropped. Log rail data");
|
||||
logPowerStatsData();
|
||||
}
|
||||
|
||||
mBatteryLevel = newBatteryLevel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public BatteryTrigger(Context context, PowerStatsLogger powerStatsLogger,
|
||||
boolean triggerEnabled) {
|
||||
super(context, powerStatsLogger);
|
||||
|
||||
if (triggerEnabled) {
|
||||
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
Intent batteryStatus = mContext.registerReceiver(mBatteryLevelReceiver, filter);
|
||||
mBatteryLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.FileRotator;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* PowerStatsDataStorage implements the on-device storage cache for energy
|
||||
* data. This data must be persisted across boot cycles so we store it
|
||||
* on-device. Versioning of this data is handled by deleting any data that
|
||||
* does not match the current version. The cache is implemented as a circular
|
||||
* buffer using the FileRotator class in android.util. We maintain 48 hours
|
||||
* worth of logs in 12 files (4 hours each).
|
||||
*/
|
||||
public class PowerStatsDataStorage {
|
||||
private static final String TAG = PowerStatsDataStorage.class.getSimpleName();
|
||||
|
||||
private static final long MILLISECONDS_PER_HOUR = 1000 * 60 * 60;
|
||||
// Rotate files every 4 hours.
|
||||
private static final long ROTATE_AGE_MILLIS = 4 * MILLISECONDS_PER_HOUR;
|
||||
// Store 48 hours worth of data.
|
||||
private static final long DELETE_AGE_MILLIS = 48 * MILLISECONDS_PER_HOUR;
|
||||
|
||||
private final ReentrantLock mLock = new ReentrantLock();
|
||||
private File mDataStorageDir;
|
||||
private final FileRotator mFileRotator;
|
||||
|
||||
private static class DataElement {
|
||||
private static final int LENGTH_FIELD_WIDTH = 4;
|
||||
private static final int MAX_DATA_ELEMENT_SIZE = 1000;
|
||||
|
||||
private byte[] mData;
|
||||
|
||||
private byte[] toByteArray() throws IOException {
|
||||
ByteArrayOutputStream data = new ByteArrayOutputStream();
|
||||
data.write(ByteBuffer.allocate(LENGTH_FIELD_WIDTH).putInt(mData.length).array());
|
||||
data.write(mData);
|
||||
return data.toByteArray();
|
||||
}
|
||||
|
||||
protected byte[] getData() {
|
||||
return mData;
|
||||
}
|
||||
|
||||
private DataElement(byte[] data) {
|
||||
mData = data;
|
||||
}
|
||||
|
||||
private DataElement(InputStream in) throws IOException {
|
||||
byte[] lengthBytes = new byte[LENGTH_FIELD_WIDTH];
|
||||
int bytesRead = in.read(lengthBytes);
|
||||
mData = new byte[0];
|
||||
|
||||
if (bytesRead == LENGTH_FIELD_WIDTH) {
|
||||
int length = ByteBuffer.wrap(lengthBytes).getInt();
|
||||
|
||||
if (0 < length && length < MAX_DATA_ELEMENT_SIZE) {
|
||||
mData = new byte[length];
|
||||
bytesRead = in.read(mData);
|
||||
|
||||
if (bytesRead != length) {
|
||||
throw new IOException("Invalid bytes read, expected: " + length
|
||||
+ ", actual: " + bytesRead);
|
||||
}
|
||||
} else {
|
||||
throw new IOException("DataElement size is invalid: " + length);
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Did not read " + LENGTH_FIELD_WIDTH + " bytes (" + bytesRead
|
||||
+ ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by external classes to read DataElements from on-device storage.
|
||||
* This callback is passed in to the read() function and is called for
|
||||
* each DataElement read from on-device storage.
|
||||
*/
|
||||
public interface DataElementReadCallback {
|
||||
/**
|
||||
* When performing a read of the on-device storage this callback
|
||||
* must be passed in to the read function. The function will be
|
||||
* called for each DataElement read from on-device storage.
|
||||
*
|
||||
* @param data Byte array containing a DataElement payload.
|
||||
*/
|
||||
void onReadDataElement(byte[] data);
|
||||
}
|
||||
|
||||
private static class DataReader implements FileRotator.Reader {
|
||||
private DataElementReadCallback mCallback;
|
||||
|
||||
DataReader(DataElementReadCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(InputStream in) throws IOException {
|
||||
while (in.available() > 0) {
|
||||
try {
|
||||
DataElement dataElement = new DataElement(in);
|
||||
mCallback.onReadDataElement(dataElement.getData());
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to read from storage. " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DataRewriter implements FileRotator.Rewriter {
|
||||
byte[] mActiveFileData;
|
||||
byte[] mNewData;
|
||||
|
||||
DataRewriter(byte[] data) {
|
||||
mActiveFileData = new byte[0];
|
||||
mNewData = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
// ignored
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(InputStream in) throws IOException {
|
||||
mActiveFileData = new byte[in.available()];
|
||||
in.read(mActiveFileData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldWrite() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
out.write(mActiveFileData);
|
||||
out.write(mNewData);
|
||||
}
|
||||
}
|
||||
|
||||
public PowerStatsDataStorage(Context context, File dataStoragePath,
|
||||
String dataStorageFilename) {
|
||||
mDataStorageDir = dataStoragePath;
|
||||
|
||||
if (!mDataStorageDir.exists() && !mDataStorageDir.mkdirs()) {
|
||||
Log.wtf(TAG, "mDataStorageDir does not exist: " + mDataStorageDir.getPath());
|
||||
mFileRotator = null;
|
||||
} else {
|
||||
// Delete files written with an old version number. The version is included in the
|
||||
// filename, so any files that don't match the current version number can be deleted.
|
||||
File[] files = mDataStorageDir.listFiles();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
if (!files[i].getName().matches(dataStorageFilename + "(.*)")) {
|
||||
files[i].delete();
|
||||
}
|
||||
}
|
||||
|
||||
mFileRotator = new FileRotator(mDataStorageDir,
|
||||
dataStorageFilename,
|
||||
ROTATE_AGE_MILLIS,
|
||||
DELETE_AGE_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes data stored in PowerStatsDataStorage to a file descriptor.
|
||||
*
|
||||
* @param data Byte array to write to on-device storage. Byte array is
|
||||
* converted to a DataElement which prefixes the payload with
|
||||
* the data length. The DataElement is then converted to a byte
|
||||
* array and written to on-device storage.
|
||||
*/
|
||||
public void write(byte[] data) {
|
||||
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);
|
||||
}
|
||||
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all DataElements stored in on-device storage. For each
|
||||
* DataElement retrieved from on-device storage, callback is called.
|
||||
*/
|
||||
public void read(DataElementReadCallback callback) throws IOException {
|
||||
mFileRotator.readMatching(new DataReader(callback), Long.MIN_VALUE, Long.MAX_VALUE);
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* PowerStatsHALWrapper is a wrapper class for the PowerStats HAL API calls.
|
||||
*/
|
||||
public final class PowerStatsHALWrapper {
|
||||
private static final String TAG = PowerStatsHALWrapper.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* IPowerStatsHALWrapper defines the interface to the PowerStatsHAL.
|
||||
*/
|
||||
public interface IPowerStatsHALWrapper {
|
||||
/**
|
||||
* Returns rail info for all available ODPM rails.
|
||||
*
|
||||
* @return array of RailInfo objects containing rail info for all
|
||||
* available rails.
|
||||
*/
|
||||
PowerStatsData.RailInfo[] readRailInfo();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
PowerStatsData.EnergyData[] readEnergyData();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
boolean initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* PowerStatsHALWrapperImpl is the implementation of the IPowerStatsHALWrapper
|
||||
* used by the PowerStatsService. Other implementations will be used by the testing
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.os.Message;
|
||||
|
||||
/**
|
||||
* PowerStatsLogTrigger is the base class for other trigger classes.
|
||||
* It provides the logPowerStatsData() function which sends a message
|
||||
* to the PowerStatsLogger to read the rail energy data and log it to
|
||||
* on-device storage. This class is abstract and cannot be instantiated.
|
||||
*/
|
||||
public abstract class PowerStatsLogTrigger {
|
||||
private static final String TAG = PowerStatsLogTrigger.class.getSimpleName();
|
||||
|
||||
protected Context mContext;
|
||||
private PowerStatsLogger mPowerStatsLogger;
|
||||
|
||||
protected void logPowerStatsData() {
|
||||
Message.obtain(
|
||||
mPowerStatsLogger,
|
||||
PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE).sendToTarget();
|
||||
}
|
||||
|
||||
public PowerStatsLogTrigger(Context context, PowerStatsLogger powerStatsLogger) {
|
||||
mContext = context;
|
||||
mPowerStatsLogger = powerStatsLogger;
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.util.proto.ProtoInputStream;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
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.
|
||||
*/
|
||||
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 IPowerStatsHALWrapper mPowerStatsHALWrapper;
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
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());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes 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.
|
||||
*/
|
||||
public void writeToFile(FileDescriptor fd) {
|
||||
if (DEBUG) Log.d(TAG, "Writing to file");
|
||||
|
||||
final ProtoOutputStream pos = new ProtoOutputStream(fd);
|
||||
|
||||
try {
|
||||
PowerStatsData railInfo = new PowerStatsData(mPowerStatsHALWrapper.readRailInfo());
|
||||
railInfo.toProto(pos);
|
||||
if (DEBUG) railInfo.print();
|
||||
|
||||
mPowerStatsDataStorage.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.
|
||||
final PowerStatsData energyData = new PowerStatsData(pis);
|
||||
energyData.toProto(pos);
|
||||
if (DEBUG) energyData.print();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to write energy data to incident report.");
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to write rail info to incident report.");
|
||||
}
|
||||
|
||||
pos.flush();
|
||||
}
|
||||
|
||||
public PowerStatsLogger(Context context, File dataStoragePath, String dataStorageFilename,
|
||||
IPowerStatsHALWrapper powerStatsHALWrapper) {
|
||||
super(Looper.getMainLooper());
|
||||
mPowerStatsHALWrapper = powerStatsHALWrapper;
|
||||
mPowerStatsDataStorage = new PowerStatsDataStorage(context, dataStoragePath,
|
||||
dataStorageFilename);
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.Environment;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
|
||||
import com.android.server.powerstats.PowerStatsHALWrapper.PowerStatsHALWrapperImpl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* This class provides a system service that estimates system power usage
|
||||
* per subsystem (modem, wifi, gps, display, etc) and provides those power
|
||||
* estimates to subscribers.
|
||||
*/
|
||||
public class PowerStatsService extends SystemService {
|
||||
private static final String TAG = PowerStatsService.class.getSimpleName();
|
||||
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 final Injector mInjector;
|
||||
|
||||
private Context mContext;
|
||||
private IPowerStatsHALWrapper mPowerStatsHALWrapper;
|
||||
private PowerStatsLogger mPowerStatsLogger;
|
||||
private BatteryTrigger mBatteryTrigger;
|
||||
private TimerTrigger mTimerTrigger;
|
||||
|
||||
@VisibleForTesting
|
||||
static class Injector {
|
||||
File createDataStoragePath() {
|
||||
return new File(Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM),
|
||||
DATA_STORAGE_SUBDIR);
|
||||
}
|
||||
|
||||
String createDataStorageFilename() {
|
||||
return DATA_STORAGE_FILENAME;
|
||||
}
|
||||
|
||||
IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() {
|
||||
return new PowerStatsHALWrapperImpl();
|
||||
}
|
||||
|
||||
PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
|
||||
String dataStorageFilename, IPowerStatsHALWrapper powerStatsHALWrapper) {
|
||||
return new PowerStatsLogger(context, dataStoragePath, dataStorageFilename,
|
||||
powerStatsHALWrapper);
|
||||
}
|
||||
|
||||
BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
|
||||
return new BatteryTrigger(context, powerStatsLogger, true /* trigger enabled */);
|
||||
}
|
||||
|
||||
TimerTrigger createTimerTrigger(Context context, PowerStatsLogger powerStatsLogger) {
|
||||
return new TimerTrigger(context, powerStatsLogger, true /* trigger enabled */);
|
||||
}
|
||||
}
|
||||
|
||||
private final class BinderService extends Binder {
|
||||
@Override
|
||||
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
||||
|
||||
if (args.length > 0 && "--proto".equals(args[0])) {
|
||||
mPowerStatsLogger.writeToFile(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBootPhase(int phase) {
|
||||
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
|
||||
onSystemServiceReady();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
|
||||
}
|
||||
|
||||
private void onSystemServiceReady() {
|
||||
mPowerStatsHALWrapper = mInjector.createPowerStatsHALWrapperImpl();
|
||||
|
||||
if (mPowerStatsHALWrapper.initialize()) {
|
||||
if (DEBUG) Log.d(TAG, "Starting PowerStatsService");
|
||||
|
||||
// Only start logger and triggers if initialization is successful.
|
||||
mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext,
|
||||
mInjector.createDataStoragePath(), mInjector.createDataStorageFilename(),
|
||||
mPowerStatsHALWrapper);
|
||||
mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
|
||||
mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
|
||||
} else {
|
||||
Log.e(TAG, "Initialization of PowerStatsHAL wrapper failed");
|
||||
}
|
||||
}
|
||||
|
||||
public PowerStatsService(Context context) {
|
||||
this(context, new Injector());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public PowerStatsService(Context context, Injector injector) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mInjector = injector;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "FrameworksServicesTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "com.android.server.powerstats"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* TimerTrigger sets a 60 second opportunistic timer using postDelayed.
|
||||
* When the timer expires a message is sent to the PowerStatsLogger to
|
||||
* read the rail energy data and log it to on-device storage.
|
||||
*/
|
||||
public final class TimerTrigger extends PowerStatsLogTrigger {
|
||||
private static final String TAG = TimerTrigger.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
// TODO(b/166689029): Make configurable through global settings.
|
||||
private static final long LOG_PERIOD_MS = 60 * 1000;
|
||||
|
||||
private final Handler mHandler;
|
||||
|
||||
private Runnable mLogData = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Do not wake the device for these messages. Opportunistically log rail data every
|
||||
// LOG_PERIOD_MS.
|
||||
mHandler.postDelayed(mLogData, LOG_PERIOD_MS);
|
||||
if (DEBUG) Log.d(TAG, "Received delayed message. Log rail data");
|
||||
logPowerStatsData();
|
||||
}
|
||||
};
|
||||
|
||||
public TimerTrigger(Context context, PowerStatsLogger powerStatsLogger,
|
||||
boolean triggerEnabled) {
|
||||
super(context, powerStatsLogger);
|
||||
mHandler = mContext.getMainThreadHandler();
|
||||
|
||||
if (triggerEnabled) {
|
||||
mLogData.run();
|
||||
}
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ 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",
|
||||
|
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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
|
@ -29,6 +29,7 @@ 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);
|
||||
@ -82,6 +83,7 @@ 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);
|
||||
|
@ -150,6 +150,7 @@ import com.android.server.policy.role.LegacyRoleResolutionPolicy;
|
||||
import com.android.server.power.PowerManagerService;
|
||||
import com.android.server.power.ShutdownThread;
|
||||
import com.android.server.power.ThermalManagerService;
|
||||
import com.android.server.powerstats.PowerStatsService;
|
||||
import com.android.server.profcollect.ProfcollectForwardingService;
|
||||
import com.android.server.recoverysystem.RecoverySystemService;
|
||||
import com.android.server.restrictions.RestrictionsManagerService;
|
||||
@ -761,6 +762,11 @@ public final class SystemServer {
|
||||
mSystemServiceManager.startService(UriGrantsManagerService.Lifecycle.class);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("StartPowerStatsService");
|
||||
// Tracks rail data to be used for power statistics.
|
||||
mSystemServiceManager.startService(PowerStatsService.class);
|
||||
t.traceEnd();
|
||||
|
||||
// Activity manager runs the show.
|
||||
t.traceBegin("StartActivityManager");
|
||||
// TODO: Might need to move after migration to WM.
|
||||
|
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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 static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
|
||||
import com.android.server.powerstats.nano.PowerStatsServiceProto;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Tests for {@link com.android.server.powerstats.PowerStatsService}.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest FrameworksServicesTests:PowerStatsServiceTest
|
||||
*/
|
||||
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 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 final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
private PowerStatsService mService;
|
||||
private File mDataStorageDir;
|
||||
private TimerTrigger mTimerTrigger;
|
||||
private PowerStatsLogger mPowerStatsLogger;
|
||||
|
||||
private final PowerStatsService.Injector mInjector = new PowerStatsService.Injector() {
|
||||
@Override
|
||||
File createDataStoragePath() {
|
||||
mDataStorageDir = null;
|
||||
|
||||
try {
|
||||
mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
|
||||
} catch (IOException e) {
|
||||
fail("Could not create temp directory.");
|
||||
}
|
||||
|
||||
return mDataStorageDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
String createDataStorageFilename() {
|
||||
return DATA_STORAGE_FILENAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() {
|
||||
return new TestPowerStatsHALWrapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
|
||||
String dataStorageFilename, IPowerStatsHALWrapper powerStatsHALWrapper) {
|
||||
mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, dataStorageFilename,
|
||||
powerStatsHALWrapper);
|
||||
return mPowerStatsLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
|
||||
return new BatteryTrigger(context, powerStatsLogger, false /* trigger enabled */);
|
||||
}
|
||||
|
||||
@Override
|
||||
TimerTrigger createTimerTrigger(Context context, PowerStatsLogger powerStatsLogger) {
|
||||
mTimerTrigger = new TimerTrigger(context, powerStatsLogger,
|
||||
false /* trigger enabled */);
|
||||
return mTimerTrigger;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
return railInfoArray;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
return energyDataArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mService = new PowerStatsService(mContext, mInjector);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrittenPowerStatsHALDataMatchesReadIncidentReportData()
|
||||
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.writeToFile(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);
|
||||
|
||||
// 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 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorruptOnDeviceStorage() 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, DATA_STORAGE_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.writeToFile(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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// No energyData should be written to the incident report since it
|
||||
// is all corrupt (random bytes generated above).
|
||||
assertTrue(pssProto.energyData.length == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEnoughBytesAfterLengthField() 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, DATA_STORAGE_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.writeToFile(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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// No energyData should be written to the incident report since the
|
||||
// input buffer had only length and no data.
|
||||
assertTrue(pssProto.energyData.length == 0);
|
||||
}
|
||||
}
|
10
tools/powerstats/Android.bp
Normal file
10
tools/powerstats/Android.bp
Normal file
@ -0,0 +1,10 @@
|
||||
java_binary_host {
|
||||
name: "PowerStatsServiceProtoParser",
|
||||
manifest: "PowerStatsServiceProtoParser_manifest.txt",
|
||||
srcs: [
|
||||
"*.java",
|
||||
],
|
||||
static_libs: [
|
||||
"platformprotos",
|
||||
],
|
||||
}
|
95
tools/powerstats/PowerStatsServiceProtoParser.java
Normal file
95
tools/powerstats/PowerStatsServiceProtoParser.java
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This class implements a utility to parse ODPM data out
|
||||
* of incident reports contained in bugreports. The data
|
||||
* is output to STDOUT in csv format.
|
||||
*/
|
||||
public class PowerStatsServiceProtoParser {
|
||||
private static void printRailInfo(PowerStatsServiceProto 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() + ",";
|
||||
}
|
||||
System.out.println(csvHeader);
|
||||
}
|
||||
|
||||
private static void printEnergyData(PowerStatsServiceProto proto) {
|
||||
int railInfoCount = proto.getRailInfoCount();
|
||||
|
||||
if (railInfoCount > 0) {
|
||||
int energyDataCount = proto.getEnergyDataCount();
|
||||
int energyDataSetCount = energyDataCount / railInfoCount;
|
||||
|
||||
for (int i = 0; i < energyDataSetCount; 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() + ",";
|
||||
}
|
||||
System.out.println(csvRow);
|
||||
}
|
||||
} else {
|
||||
System.out.println("Error: railInfoCount is zero");
|
||||
}
|
||||
}
|
||||
|
||||
private static void generateCsvFile(String pathToIncidentReport) {
|
||||
try {
|
||||
IncidentReportProto irProto =
|
||||
IncidentReportProto.parseFrom(new FileInputStream(pathToIncidentReport));
|
||||
|
||||
if (irProto.hasIncidentReport()) {
|
||||
PowerStatsServiceProto pssProto = irProto.getIncidentReport();
|
||||
printRailInfo(pssProto);
|
||||
printEnergyData(pssProto);
|
||||
} else {
|
||||
System.out.println("Incident report not found. Exiting.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println("Unable to open incident report file: " + pathToIncidentReport);
|
||||
System.out.println(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the entry point to parse the ODPM data out of incident reports.
|
||||
* It requires one argument which is the path to the incident_report.proto
|
||||
* file captured in a bugreport.
|
||||
*
|
||||
* @param args Path to incident_report.proto passed in from command line.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
if (args.length > 0) {
|
||||
generateCsvFile(args[0]);
|
||||
} else {
|
||||
System.err.println("Usage: PowerStatsServiceProtoParser <incident_report.proto>");
|
||||
System.err.println("Missing path to incident_report.proto. Exiting.");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
Main-class: com.android.server.powerstats.PowerStatsServiceProtoParser
|
Loading…
x
Reference in New Issue
Block a user