Calculate battery charge from energy and voltage

Battery capacity can be reported in charge (in µAh) or energy (µWh)
counters on linux sysfs. For batteries that report energy counters,
record the energy and voltage values and convert to charge in the trace processor.

Bug: 268144878
Change-Id: Ie892a36ffab14a1b994426197d7d788b79530103
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index a215592..fc47541 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -10204,6 +10204,12 @@
 
   // Battery name, emitted only on multiple batteries.
   optional string name = 5;
+
+  // Battery capacity in microwatt-hours(µWh).
+  optional int64 energy_counter_uwh = 6;
+
+  // Battery voltage in microvolts(µV).
+  optional int64 voltage_uv = 7;
 }
 
 // End of protos/perfetto/trace/power/battery_counters.proto
diff --git a/protos/perfetto/trace/power/battery_counters.proto b/protos/perfetto/trace/power/battery_counters.proto
index ab1aaa8..bb1a23c 100644
--- a/protos/perfetto/trace/power/battery_counters.proto
+++ b/protos/perfetto/trace/power/battery_counters.proto
@@ -35,4 +35,10 @@
 
   // Battery name, emitted only on multiple batteries.
   optional string name = 5;
+
+  // Battery capacity in microwatt-hours(µWh).
+  optional int64 energy_counter_uwh = 6;
+
+  // Battery voltage in microvolts(µV).
+  optional int64 voltage_uv = 7;
 }
diff --git a/src/trace_processor/importers/proto/android_probes_parser.cc b/src/trace_processor/importers/proto/android_probes_parser.cc
index d4018e3..f85db0b 100644
--- a/src/trace_processor/importers/proto/android_probes_parser.cc
+++ b/src/trace_processor/importers/proto/android_probes_parser.cc
@@ -110,7 +110,18 @@
         context_->track_tracker->InternGlobalCounterTrack(batt_charge_id);
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(evt.charge_counter_uah()), track);
+  } else if (evt.has_energy_counter_uwh() && evt.has_voltage_uv()) {
+    // Calculate charge counter from energy counter and voltage.
+    TrackId track =
+        context_->track_tracker->InternGlobalCounterTrack(batt_charge_id);
+    auto energy = evt.energy_counter_uwh();
+    auto voltage = evt.voltage_uv();
+    if (voltage > 0) {
+      context_->event_tracker->PushCounter(
+          ts, static_cast<double>(energy * 1000000 / voltage), track);
+    }
   }
+
   if (evt.has_capacity_percent()) {
     TrackId track =
         context_->track_tracker->InternGlobalCounterTrack(batt_capacity_id);
diff --git a/src/traced/probes/power/linux_power_sysfs_data_source.cc b/src/traced/probes/power/linux_power_sysfs_data_source.cc
index b4bdec6..9d4365b 100644
--- a/src/traced/probes/power/linux_power_sysfs_data_source.cc
+++ b/src/traced/probes/power/linux_power_sysfs_data_source.cc
@@ -85,6 +85,21 @@
 }
 
 base::Optional<int64_t>
+LinuxPowerSysfsDataSource::BatteryInfo::GetEnergyCounterUah(
+    size_t battery_idx) {
+  PERFETTO_CHECK(battery_idx < sysfs_battery_subdirs_.size());
+  return ReadFileAsInt64(power_supply_dir_path_ + "/" +
+                         sysfs_battery_subdirs_[battery_idx] + "/energy_now");
+}
+
+base::Optional<int64_t> LinuxPowerSysfsDataSource::BatteryInfo::GetVoltageUv(
+    size_t battery_idx) {
+  PERFETTO_CHECK(battery_idx < sysfs_battery_subdirs_.size());
+  return ReadFileAsInt64(power_supply_dir_path_ + "/" +
+                         sysfs_battery_subdirs_[battery_idx] + "/voltage_now");
+}
+
+base::Optional<int64_t>
 LinuxPowerSysfsDataSource::BatteryInfo::GetCapacityPercent(size_t battery_idx) {
   PERFETTO_CHECK(battery_idx < sysfs_battery_subdirs_.size());
   return ReadFileAsInt64(power_supply_dir_path_ + "/" +
@@ -174,6 +189,12 @@
     value = battery_info_->GetAverageCurrentUa(battery_idx);
     if (value)
       counters_proto->set_current_ua(*value);
+    value = battery_info_->GetEnergyCounterUah(battery_idx);
+    if (value)
+      counters_proto->set_energy_counter_uwh(*value);
+    value = battery_info_->GetVoltageUv(battery_idx);
+    if (value)
+      counters_proto->set_voltage_uv(*value);
     // On systems with multiple batteries, disambiguate with battery names.
     if (battery_info_->num_batteries() > 1)
       counters_proto->set_name(battery_info_->GetBatteryName(battery_idx));
diff --git a/src/traced/probes/power/linux_power_sysfs_data_source.h b/src/traced/probes/power/linux_power_sysfs_data_source.h
index d757b38..058ccd6 100644
--- a/src/traced/probes/power/linux_power_sysfs_data_source.h
+++ b/src/traced/probes/power/linux_power_sysfs_data_source.h
@@ -41,6 +41,12 @@
     // The current coloumb counter value in µAh.
     base::Optional<int64_t> GetChargeCounterUah(size_t battery_idx);
 
+    // The current energy counter in µWh.
+    base::Optional<int64_t> GetEnergyCounterUah(size_t battery_idx);
+
+    // The voltage in µV.
+    base::Optional<int64_t> GetVoltageUv(size_t battery_idx);
+
     // The battery capacity in percent.
     base::Optional<int64_t> GetCapacityPercent(size_t battery_idx);
 
diff --git a/src/traced/probes/power/linux_power_sysfs_data_source_unittest.cc b/src/traced/probes/power/linux_power_sysfs_data_source_unittest.cc
index ff98604..2c1c8aa 100644
--- a/src/traced/probes/power/linux_power_sysfs_data_source_unittest.cc
+++ b/src/traced/probes/power/linux_power_sysfs_data_source_unittest.cc
@@ -104,5 +104,41 @@
   EXPECT_EQ(*battery_info_->GetChargeCounterUah(main_battery_idx), 3074000);
 }
 
+TEST(LinuxPowerSysfsDataSourceTest, EnergyNow) {
+  base::TmpDirTree tmpdir;
+  std::unique_ptr<LinuxPowerSysfsDataSource::BatteryInfo> battery_info_;
+
+  tmpdir.AddDir("BAT0");
+  tmpdir.AddFile("BAT0/type", "Battery\n");
+  tmpdir.AddFile("BAT0/present", "1\n");
+  tmpdir.AddFile("BAT0/capacity", "95\n");          // 95 percent.
+  tmpdir.AddFile("BAT0/energy_now", "56680000\n");  // 56680000 µWh.
+
+  battery_info_.reset(
+      new LinuxPowerSysfsDataSource::BatteryInfo(tmpdir.path().c_str()));
+
+  EXPECT_EQ(battery_info_->num_batteries(), 1u);
+  EXPECT_EQ(*battery_info_->GetCapacityPercent(0), 95);
+  EXPECT_EQ(*battery_info_->GetEnergyCounterUah(0), 56680000);
+}
+
+TEST(LinuxPowerSysfsDataSourceTest, EnergyVoltageNow) {
+  base::TmpDirTree tmpdir;
+  std::unique_ptr<LinuxPowerSysfsDataSource::BatteryInfo> battery_info_;
+
+  tmpdir.AddDir("BAT0");
+  tmpdir.AddFile("BAT0/type", "Battery\n");
+  tmpdir.AddFile("BAT0/present", "1\n");
+  tmpdir.AddFile("BAT0/capacity", "95\n");           // 95 percent.
+  tmpdir.AddFile("BAT0/voltage_now", "17356000\n");  // Now at 17.356 µV.
+
+  battery_info_.reset(
+      new LinuxPowerSysfsDataSource::BatteryInfo(tmpdir.path().c_str()));
+
+  EXPECT_EQ(battery_info_->num_batteries(), 1u);
+  EXPECT_EQ(*battery_info_->GetCapacityPercent(0), 95);
+  EXPECT_EQ(*battery_info_->GetVoltageUv(0), 17356000);
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index 09e084e..4367919 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -56,6 +56,7 @@
 from diff_tests.performance.tests import Performance
 from diff_tests.power.tests import Power
 from diff_tests.power.tests_energy_breakdown import PowerEnergyBreakdown
+from diff_tests.power.tests_linux_sysfs_power import LinuxSysfsPower
 from diff_tests.power.tests_power_rails import PowerPowerRails
 from diff_tests.power.tests_voltage_and_scaling import PowerVoltageAndScaling
 from diff_tests.process_tracking.tests import ProcessTracking
@@ -113,6 +114,7 @@
       *GraphicsDrmRelatedFtraceEvents(index_path, 'graphics',
                                       'GraphicsDrmRelatedFtraceEvents').fetch(),
       *Ufs(index_path, 'ufs', 'Ufs').fetch(),
+      *LinuxSysfsPower(index_path, 'power', 'LinuxSysfsPower').fetch(),
       *Memory(index_path, 'memory', 'Memory').fetch(),
       *MemoryMetrics(index_path, 'memory', 'MemoryMetrics').fetch(),
       *Network(index_path, 'network', 'Network').fetch(),
diff --git a/test/trace_processor/diff_tests/power/tests_linux_sysfs_power.py b/test/trace_processor/diff_tests/power/tests_linux_sysfs_power.py
new file mode 100644
index 0000000..fce058e
--- /dev/null
+++ b/test/trace_processor/diff_tests/power/tests_linux_sysfs_power.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+# Copyright (C) 2023 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+from python.generators.diff_tests.testing import Csv, Json, TextProto
+from python.generators.diff_tests.testing import DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class LinuxSysfsPower(TestSuite):
+
+  # Test basic battery counters.
+  def test_counters(self):
+    return DiffTestBlueprint(
+        trace=TextProto("""
+        packet {
+          timestamp: 3000000
+          battery {
+            charge_counter_uah: 3005000
+            capacity_percent: 100.000000
+            current_ua: 0
+          }
+        }
+        """),
+        query="""
+        SELECT * FROM (
+          (SELECT AVG(value) AS capacity_percent FROM counters
+           WHERE name='batt.capacity_pct'),
+          (SELECT AVG(value) AS charge_uah FROM counters
+           WHERE name='batt.charge_uah'),
+          (SELECT AVG(value) AS current_ua FROM counters
+           WHERE name='batt.current_ua')
+        );
+        """,
+        out=Csv("""
+        "capacity_percent","charge_uah","current_ua"
+        100.000000,3005000.000000,0.000000
+        """))
+
+  # Test multiple batteries.
+  def test_multiple_batteries(self):
+    return DiffTestBlueprint(
+        trace=TextProto("""
+        packet {
+          timestamp: 3000000
+          battery {
+            charge_counter_uah: 3005000
+            capacity_percent: 100.000000
+            current_ua: 0
+            name: "BAT0"
+          }
+        }
+        packet {
+          timestamp: 3000000
+          battery {
+            capacity_percent: 90.000000
+            name: "BAT1"
+          }
+        }
+        """),
+        query="""
+        SELECT name, value FROM counters WHERE name like "batt.%" ORDER BY name
+        """,
+        out=Csv("""
+        "name","value"
+        "batt.BAT0.capacity_pct",100.000000
+        "batt.BAT0.charge_uah",3005000.000000
+        "batt.BAT0.current_ua",0.000000
+        "batt.BAT1.capacity_pct",90.000000
+        """))
+
+  # Test convertion to charge counter from energy and voltage.
+  def test_charge_from_energy_and_voltage(self):
+    return DiffTestBlueprint(
+        trace=TextProto("""
+        packet {
+          timestamp: 3000000
+          battery {
+            energy_counter_uwh: 56680000
+            voltage_uv: 17356000
+          }
+        }
+        packet {
+          timestamp: 4000000
+          battery {
+            energy_counter_uwh: 56600000
+            voltage_uv: 17356000
+          }
+        }
+        """),
+        query="""
+        SELECT value
+        FROM counters
+        WHERE name = "batt.charge_uah"
+        """,
+        out=Csv("""
+        "value"
+        3265729.000000
+        3261120.000000
+        """))
+
+  # Test convertion to charge counter from energy and voltage: bad voltage
+  # value.
+  def test_charge_from_energy_and_bad_voltage(self):
+    return DiffTestBlueprint(
+        trace=TextProto("""
+        packet {
+          timestamp: 3000000
+          battery {
+            energy_counter_uwh: 56680000
+            voltage_uv: 0
+          }
+        }
+        """),
+        query="""
+        SELECT value
+        FROM counters
+        WHERE name = "batt.charge_uah"
+        """,
+        out=Csv("""
+        "value"
+        """))