Log energy consumer result attribution data

Bug: 175723658
Test: atest FrameworksServicesTests:PowerStatsServiceTest
Change-Id: I4d0dccd440b02a35e086b08e31b1bf1f087f61e3
diff --git a/core/proto/android/server/powerstatsservice.proto b/core/proto/android/server/powerstatsservice.proto
index 0c5a360..4b1ee02 100644
--- a/core/proto/android/server/powerstatsservice.proto
+++ b/core/proto/android/server/powerstatsservice.proto
@@ -191,6 +191,14 @@
     optional string name = 4;
 }
 
+message EnergyConsumerAttributionProto {
+    /** Android ID / Linux UID, the accumulated energy should be attributed to. */
+    optional int32 uid = 1;
+
+    /** Accumulated energy since boot in microwatt-seconds (uWs) for this AID. */
+    optional int64 energy_uws = 2;
+}
+
 /**
  * Energy consumer result:
  * An estimate of energy consumption since boot for the subsystem identified
@@ -205,6 +213,9 @@
 
     /** Accumulated energy since device boot in microwatt-seconds (uWs) */
     optional int64 energy_uws = 3;
+
+    /** Optional attribution per UID for this EnergyConsumer. */
+    repeated EnergyConsumerAttributionProto attribution = 4;
 }
 
 /**
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 78a227e..88090b3 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -53,8 +53,9 @@
 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_TIMER = 0;
-    protected static final int MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP = 1;
+    protected static final int MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP = 0;
+    protected static final int MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY = 1;
+    protected static final int MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY = 2;
 
     private final PowerStatsDataStorage mPowerStatsMeterStorage;
     private final PowerStatsDataStorage mPowerStatsModelStorage;
@@ -64,8 +65,8 @@
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-            case MSG_LOG_TO_DATA_STORAGE_TIMER:
-                if (DEBUG) Slog.d(TAG, "Logging to data storage on timer");
+            case MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY:
+                if (DEBUG) Slog.d(TAG, "Logging to data storage on high frequency timer");
 
                 // Log power meter data.
                 EnergyMeasurement[] energyMeasurements =
@@ -74,12 +75,23 @@
                         EnergyMeasurementUtils.getProtoBytes(energyMeasurements));
                 if (DEBUG) EnergyMeasurementUtils.print(energyMeasurements);
 
-                // Log power model data.
-                EnergyConsumerResult[] energyConsumerResults =
+                // Log power model data without attribution data.
+                EnergyConsumerResult[] ecrNoAttribution =
                     mPowerStatsHALWrapper.getEnergyConsumed(new int[0]);
                 mPowerStatsModelStorage.write(
-                        EnergyConsumerResultUtils.getProtoBytes(energyConsumerResults));
-                if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResults);
+                        EnergyConsumerResultUtils.getProtoBytes(ecrNoAttribution, false));
+                if (DEBUG) EnergyConsumerResultUtils.print(ecrNoAttribution);
+                break;
+
+            case MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY:
+                if (DEBUG) Slog.d(TAG, "Logging to data storage on low frequency timer");
+
+                // Log power model data with attribution data.
+                EnergyConsumerResult[] ecrAttribution =
+                    mPowerStatsHALWrapper.getEnergyConsumed(new int[0]);
+                mPowerStatsModelStorage.write(
+                        EnergyConsumerResultUtils.getProtoBytes(ecrAttribution, true));
+                if (DEBUG) EnergyConsumerResultUtils.print(ecrAttribution);
                 break;
 
             case MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP:
@@ -163,7 +175,7 @@
                         // deserialize, then re-serialize.  This is computationally inefficient.
                         EnergyConsumerResult[] energyConsumerResult =
                             EnergyConsumerResultUtils.unpackProtoMessage(data);
-                        EnergyConsumerResultUtils.packProtoMessage(energyConsumerResult, pos);
+                        EnergyConsumerResultUtils.packProtoMessage(energyConsumerResult, pos, true);
                         if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResult);
                     } catch (IOException e) {
                         Slog.e(TAG, "Failed to write energy model data to incident report.");
diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
index 766cf9c..bd003d3 100644
--- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
+++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
@@ -18,6 +18,7 @@
 
 import android.hardware.power.stats.Channel;
 import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyMeasurement;
 import android.hardware.power.stats.PowerEntity;
@@ -433,23 +434,40 @@
     }
 
     static class EnergyConsumerResultUtils {
-        public static byte[] getProtoBytes(EnergyConsumerResult[] energyConsumerResult) {
+        public static byte[] getProtoBytes(EnergyConsumerResult[] energyConsumerResult,
+                boolean includeAttribution) {
             ProtoOutputStream pos = new ProtoOutputStream();
-            packProtoMessage(energyConsumerResult, pos);
+            packProtoMessage(energyConsumerResult, pos, includeAttribution);
             return pos.getBytes();
         }
 
         public static void packProtoMessage(EnergyConsumerResult[] energyConsumerResult,
-                ProtoOutputStream pos) {
+                ProtoOutputStream pos, boolean includeAttribution) {
             if (energyConsumerResult == null) return;
 
             for (int i = 0; i < energyConsumerResult.length; i++) {
-                long token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
+                long ecrToken = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
                 pos.write(EnergyConsumerResultProto.ID, energyConsumerResult[i].id);
                 pos.write(EnergyConsumerResultProto.TIMESTAMP_MS,
                         energyConsumerResult[i].timestampMs);
                 pos.write(EnergyConsumerResultProto.ENERGY_UWS, energyConsumerResult[i].energyUWs);
-                pos.end(token);
+
+                if (includeAttribution) {
+                    final int attributionLength = energyConsumerResult[i].attribution.length;
+
+                    for (int j = 0; j < attributionLength; j++) {
+                        final EnergyConsumerAttribution energyConsumerAttribution =
+                                energyConsumerResult[i].attribution[j];
+                        final long ecaToken = pos.start(EnergyConsumerResultProto.ATTRIBUTION);
+                        pos.write(EnergyConsumerAttributionProto.UID,
+                                energyConsumerAttribution.uid);
+                        pos.write(EnergyConsumerAttributionProto.ENERGY_UWS,
+                                energyConsumerAttribution.energyUWs);
+                        pos.end(ecaToken);
+                    }
+                }
+
+                pos.end(ecrToken);
             }
         }
 
@@ -480,9 +498,45 @@
             }
         }
 
+        private static EnergyConsumerAttribution unpackEnergyConsumerAttributionProto(
+                ProtoInputStream pis) throws IOException {
+            final EnergyConsumerAttribution energyConsumerAttribution =
+                    new EnergyConsumerAttribution();
+
+            while (true) {
+                try {
+                    switch (pis.nextField()) {
+                        case (int) EnergyConsumerAttributionProto.UID:
+                            energyConsumerAttribution.uid =
+                                pis.readInt(EnergyConsumerAttributionProto.UID);
+                            break;
+
+                        case (int) EnergyConsumerAttributionProto.ENERGY_UWS:
+                            energyConsumerAttribution.energyUWs =
+                                pis.readLong(EnergyConsumerAttributionProto.ENERGY_UWS);
+                            break;
+
+                        case ProtoInputStream.NO_MORE_FIELDS:
+                            return energyConsumerAttribution;
+
+                        default:
+                            Slog.e(TAG, "Unhandled field in EnergyConsumerAttributionProto: "
+                                    + ProtoUtils.currentFieldToString(pis));
+                            break;
+
+                    }
+                } catch (WireTypeMismatchException wtme) {
+                    Slog.e(TAG, "Wire Type mismatch in EnergyConsumerAttributionProto: "
+                            + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        }
+
         private static EnergyConsumerResult unpackEnergyConsumerResultProto(ProtoInputStream pis)
                 throws IOException {
             EnergyConsumerResult energyConsumerResult = new EnergyConsumerResult();
+            final List<EnergyConsumerAttribution> energyConsumerAttributionList =
+                    new ArrayList<EnergyConsumerAttribution>();
 
             while (true) {
                 try {
@@ -501,7 +555,18 @@
                                 pis.readLong(EnergyConsumerResultProto.ENERGY_UWS);
                             break;
 
+                        case (int) EnergyConsumerResultProto.ATTRIBUTION:
+                            final long token = pis.start(EnergyConsumerResultProto.ATTRIBUTION);
+                            energyConsumerAttributionList.add(
+                                    unpackEnergyConsumerAttributionProto(pis));
+                            pis.end(token);
+                            break;
+
                         case ProtoInputStream.NO_MORE_FIELDS:
+                            energyConsumerResult.attribution =
+                                energyConsumerAttributionList.toArray(
+                                    new EnergyConsumerAttribution[
+                                        energyConsumerAttributionList.size()]);
                             return energyConsumerResult;
 
                         default:
@@ -520,9 +585,16 @@
             if (energyConsumerResult == null) return;
 
             for (int i = 0; i < energyConsumerResult.length; i++) {
-                Slog.d(TAG, "EnergyConsumerId: " + energyConsumerResult[i].id
-                        + ", Timestamp (ms): " + energyConsumerResult[i].timestampMs
-                        + ", Energy (uWs): " + energyConsumerResult[i].energyUWs);
+                final EnergyConsumerResult result = energyConsumerResult[i];
+                Slog.d(TAG, "EnergyConsumerId: " + result.id
+                        + ", Timestamp (ms): " + result.timestampMs
+                        + ", Energy (uWs): " + result.energyUWs);
+                final int attributionLength = result.attribution.length;
+                for (int j = 0; j < attributionLength; j++) {
+                    final EnergyConsumerAttribution attribution = result.attribution[j];
+                    Slog.d(TAG, "  UID: " + attribution.uid
+                            + "  Energy (uWs): " + attribution.energyUWs);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/powerstats/TimerTrigger.java b/services/core/java/com/android/server/powerstats/TimerTrigger.java
index 7cba00f..f8a4135 100644
--- a/services/core/java/com/android/server/powerstats/TimerTrigger.java
+++ b/services/core/java/com/android/server/powerstats/TimerTrigger.java
@@ -29,18 +29,30 @@
     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 = 120 * 1000;
+    private static final long LOG_PERIOD_MS_LOW_FREQUENCY = 60 * 60 * 1000; // 1 hour
+    private static final long LOG_PERIOD_MS_HIGH_FREQUENCY = 2 * 60 * 1000; // 2 minutes
 
     private final Handler mHandler;
 
-    private Runnable mLogData = new Runnable() {
+    private Runnable mLogDataLowFrequency = 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) Slog.d(TAG, "Received delayed message.  Log rail data");
-            logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_TIMER);
+            // LOG_PERIOD_MS_LOW_FREQUENCY.
+            mHandler.postDelayed(mLogDataLowFrequency, LOG_PERIOD_MS_LOW_FREQUENCY);
+            if (DEBUG) Slog.d(TAG, "Received delayed message.  Log rail data low frequency");
+            logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY);
+        }
+    };
+
+    private Runnable mLogDataHighFrequency = new Runnable() {
+        @Override
+        public void run() {
+            // Do not wake the device for these messages.  Opportunistically log rail data every
+            // LOG_PERIOD_MS_HIGH_FREQUENCY.
+            mHandler.postDelayed(mLogDataHighFrequency, LOG_PERIOD_MS_HIGH_FREQUENCY);
+            if (DEBUG) Slog.d(TAG, "Received delayed message.  Log rail data high frequency");
+            logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY);
         }
     };
 
@@ -50,7 +62,8 @@
         mHandler = mContext.getMainThreadHandler();
 
         if (triggerEnabled) {
-            mLogData.run();
+            mLogDataLowFrequency.run();
+            mLogDataHighFrequency.run();
         }
     }
 }
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 84b690f..49ee565 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.hardware.power.stats.Channel;
 import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyMeasurement;
 import android.hardware.power.stats.PowerEntity;
@@ -73,6 +74,7 @@
     private static final String ENERGY_CONSUMER_NAME = "energyconsumer";
     private static final int ENERGY_METER_COUNT = 8;
     private static final int ENERGY_CONSUMER_COUNT = 2;
+    private static final int ENERGY_CONSUMER_ATTRIBUTION_COUNT = 5;
     private static final int POWER_ENTITY_COUNT = 3;
     private static final int STATE_INFO_COUNT = 5;
     private static final int STATE_RESIDENCY_COUNT = 4;
@@ -204,6 +206,13 @@
                 energyConsumedList[i].id = i;
                 energyConsumedList[i].timestampMs = i;
                 energyConsumedList[i].energyUWs = i;
+                energyConsumedList[i].attribution =
+                    new EnergyConsumerAttribution[ENERGY_CONSUMER_ATTRIBUTION_COUNT];
+                for (int j = 0; j < energyConsumedList[i].attribution.length; j++) {
+                    energyConsumedList[i].attribution[j] = new EnergyConsumerAttribution();
+                    energyConsumedList[i].attribution[j].uid = j;
+                    energyConsumedList[i].attribution[j].energyUWs = j;
+                }
             }
             return energyConsumedList;
         }
@@ -250,7 +259,7 @@
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
 
         // Write data to on-device storage.
-        mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_TIMER);
+        mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY);
 
         // The above call puts a message on a handler.  Wait for
         // it to be processed.
@@ -293,7 +302,7 @@
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
 
         // Write data to on-device storage.
-        mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_TIMER);
+        mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY);
 
         // The above call puts a message on a handler.  Wait for
         // it to be processed.
@@ -324,6 +333,12 @@
             assertTrue(pssProto.energyConsumerResult[i].id == i);
             assertTrue(pssProto.energyConsumerResult[i].timestampMs == i);
             assertTrue(pssProto.energyConsumerResult[i].energyUws == i);
+            assertTrue(pssProto.energyConsumerResult[i].attribution.length
+                    == ENERGY_CONSUMER_ATTRIBUTION_COUNT);
+            for (int j = 0; j < pssProto.energyConsumerResult[i].attribution.length; j++) {
+                assertTrue(pssProto.energyConsumerResult[i].attribution[j].uid == j);
+                assertTrue(pssProto.energyConsumerResult[i].attribution[j].energyUws  == j);
+            }
         }
     }
 
diff --git a/tools/powerstats/PowerStatsServiceProtoParser.java b/tools/powerstats/PowerStatsServiceProtoParser.java
index 2140954..04f9bf2 100644
--- a/tools/powerstats/PowerStatsServiceProtoParser.java
+++ b/tools/powerstats/PowerStatsServiceProtoParser.java
@@ -86,6 +86,12 @@
                     csvRow += energyConsumerResult.getId() + ","
                         + energyConsumerResult.getTimestampMs() + ","
                         + energyConsumerResult.getEnergyUws() + ",";
+                    for (int k = 0; k < energyConsumerResult.getAttributionCount(); k++) {
+                        final EnergyConsumerAttributionProto energyConsumerAttribution =
+                                energyConsumerResult.getAttribution(k);
+                        csvRow += energyConsumerAttribution.getUid() + ","
+                            + energyConsumerAttribution.getEnergyUws() + ",";
+                    }
                 }
                 System.out.println(csvRow);
             }