ARM-software / devlib

Library for interaction with and instrumentation of remote devices.
Apache License 2.0
47 stars 78 forks source link

Feature Request: Android battery drain measurement #522

Open qais-yousef opened 3 years ago

qais-yousef commented 3 years ago

Android provides an interface to access battery counters to measure battery drain

https://source.android.com/devices/tech/power/device https://perfetto.dev/docs/data-sources/battery-counters

Is it possible for devlib to support this API to help with measuring power consumption of an Android device?

Thanks.

marcbonnici commented 3 years ago

This looks like it could be interesting to investigate. Would the aim here to be to expose a similar set of properties as the "battery fuel gauge"?

setrofim commented 3 years ago

I believe that, on some devices at least, battery status may already be exposed via sysfs and can be collected via existing instrumentation. However, it may be worth adding a probe that uses the standard Android API as a more portable solution...

qais-yousef commented 3 years ago

Sorry I should have mentioned the goal of why I'm requesting this too!

The idea is that being able to use WorkloadAutomation to run performance test on Android devices and use this interface to collect power measurements to see the impact. Ultimately helping us do A/B comparison after a code change. Since instrumenting the device for power measurement is not easy, using this API hopefully can give us something generic to work with over a bigger range of devices/platforms.

marcbonnici commented 3 years ago

Looking into this a bit more it seems the simplest way to get basic battery stats from the system would be via dumpsys battery which gives an output similar to following which we could expose:

Current Battery Service state:
  AC powered: false
  USB powered: true
  Wireless powered: false
  Max charging current: 500000
  Max charging voltage: 5000000
  Charge counter: 2383748
  status: 3
  health: 2
  present: true
  level: 93
  scale: 100
  voltage: 4089
  batteryPct=(level*100)/scale

Would these, or a subset of, metrics be sufficient for your usecase?

I'm also wondering if this is to be used for A/B comparison, would you expect to ensure the device had the same starting state and then to take a reading at the start of a job and again at the end of the run comparing the decrease in battery power?

qais-yousef commented 3 years ago

I need to revisit the dumpsys battery. But it's not what I'm after here.

If you try an app like Battery Charging Monitor: https://play.google.com/store/apps/details?id=com.tofabd.batteryanalyzer&hl=en_GB&gl=US

You'll see that it gives real time reading of the current drawn from the battery. So my idea was to reset the counter, start an experiment and dump the collected samples of current in experiment A. Then modify the code and re-run experiment B. If my modification in experiment B saves power, the Sum of current samples in B should be smaller than A.

One issue is when the USB cable is connected, it has to be set not to charge. Though it's not a requirement. I ran an experiment of running Geekbench when it's charging and I think it should be usable. Ie: when USB is not charing I see the current in the negative ~ -500uA. When it is charging it is at ~ +1250uA then drops to ~ +700uA when Geekbench is running. So we can observe the difference.

In my example, if the device is charging then the Sum of Exp. B should be higher to indicate power save (it has charged the battery more than Exp. A).

If we can disable charging when running a test that'd be better but I need to look up how feasible is this.

Hopefully this makes sense?

marcbonnici commented 3 years ago

One issue is when the USB cable is connected, it has to be set not to charge. Though it's not a requirement.

I think that this will be a requirement to be able to get comparable readings as the level of the battery can greatly affect the amount of current being drawn, if the battery is nearly empty you are likely to obtain much larger values that if running with the battery nearly full and this could greatly skew the results. I would interested to see if you repeated your test at differing charge levels whether this would be shown via the application?

On this topic I have found there are some applications that attempt to control the charging behavior however it looks like this is not standardised and they attempt to do this by maintaining a list of common nodes exposed via sysfs. E.g. from: https://github.com/sriharshaarangi/BatteryChargeLimit/blob/ff7360c6048475f1843628c6d47a79313e87f6af/app/src/main/res/raw/control_files.json

/sys/class/hw_power/charger/charge_data/enable_charger
/sys/class/power_supply/ac/charging_enabled
/sys/class/power_supply/battery/batt_slate_mode
/sys/class/power_supply/battery/battery_charging_enabled
...

If you try an app like Battery Charging Monitor ...

I see, I think I understand your aim better now thanks. I had a look at similar application "Ampere" to see how they were managing to read the current value and again it looks like they are attempting to file and read the value as exposed via sysfs. (https://forum.xda-developers.com/t/app-4-0-3-ampere-current-meter.3040329/#post-59086006)

The list of currently scanned file interfaces:
    /sys/class/power_supply/battery/batt_current
    /sys/class/power_supply/battery/batt_current_adc
    /sys/class/power_supply/battery/batt_current_now
    /sys/class/power_supply/battery/BatteryAverageCurrent
    ...

Android provides an interface to access battery counters to measure battery drain

I believe that, on some devices at least, battery status may already be exposed via sysfs and can be collected via existing instrumentation. However, it may be worth adding a probe that uses the standard Android API as a more portable solution...

I tried printing these properties out from the API on my nexus 5 and received the following values:

Remaining energy = -9223372036854775808
Average energy = -9223372036854775808
Current energy = -771174

The "current energy" value looked it matches what was reported by the sysfs node but the others look like garbage values so I'm not sure how reliable these would be on other devices and the documentation only seems to talk about nexus devices. Although as the applications I found are still relying on sysfs rather than using the android API I wonder if that could be an indication...


So overall it seems to me that the aim here to try and minimise additional influences would be to ensure that battery charging was disabled, and then to sample the reported voltage and current values (via some mechanism) to estimate the amount of energy consumed over the an amount of time. I suspect these reading may still be rather inaccurate compared to hardware instrumentation however it might be enough to provide A/B comparisons for multiple runs on the same device.

Does that seem reasonable?

JaviMerino commented 3 years ago

Please don't gate this on the ability to stop USB charging. There are many ways of avoiding them, one of them is just with adb over wifi. Let's limit the scope of this to just battery draining. The documentation can include a warning about the battery not being charged while using the instrument.

qais-yousef commented 3 years ago

I think that this will be a requirement to be able to get comparable readings as the level of the battery can greatly affect the amount of current being drawn, if the battery is nearly empty you are likely to obtain much larger values that if running with the battery nearly full and this could greatly skew the results. I would interested to see if you repeated your test at differing charge levels whether this would be shown via the application?

As Javi already pointed out, the USB issue isn't a show stopper.

I will try to schedule an experiment next week to see how current reading would differ depending on battery charge. Accuracy can be an issue but I think the limitation it will impose is on the length of the experiment one can conduct only. If each experiments take few mins I'd expected A/B comparison to be fairly representative. But if each one takes 2 hours, then a total run time of 4 hours could be suffering from the accuracy problems, yes. I will try to collect some data.

The "current energy" value looked it matches what was reported by the sysfs node but the others look like garbage values so I'm not sure how reliable these would be on other devices and the documentation only seems to talk about nexus devices. Although as the applications I found are still relying on sysfs rather than using the android API I wonder if that could be an indication...

If the API is not reliable that would be a bummer actually. I thought these apps are using it :-/

Does that seem reasonable?

Yes. In a lot of cases it'll help doing more tests and measurements on desk without special instrumentation; especially in the WFH environment we're in now. IMO it should be good enough to run 10 mins test for experiment A, then flash new kernel and run experiment B for another 10 mins and see if things are any better.

marcbonnici commented 3 years ago

I will try to schedule an experiment next week to see how current reading would differ depending on battery charge. Accuracy can be an issue but I think the limitation it will impose is on the length of the experiment one can conduct only. If each experiments take few mins I'd expected A/B comparison to be fairly representative. But if each one takes 2 hours, then a total run time of 4 hours could be suffering from the accuracy problems, yes. I will try to collect some data.

I've put together a WIP instrument to try and help with this investigation [1] [2]. It aims to search for a valid sysfs node for current readings and polls this along with dumpsys to obtain voltage readings. With this it is usually just about able to distinguish between an idle workload and running dhrystone but the readings are not at all consistent between multiple runs. Unfortunately I currently only have a nexus 5 to test this on so it would be interesting to see if this behavior also applies to other devices.

The instrument will save it's raw readings in the workload directory and adds a summary with the derived energy metrics to estimated the total energy and average battery power.

At it's very best the outputs looks something like the following. The jobs are 30 seconds each, have battery charging manually disabled and uses a fixed delay of 30 seconds between each job to try and allow time for the readings to update between each job.

wk1, Dhrystone - 1 Thread,1,battery_total_energy,22.043696616739986,joules
wk1, Dhrystone - 1 Thread,1,battery_average_power,0.7399434510000003,watts
..
wk2,Dhrystone  - 4 Thread,1,battery_total_energy,32.05555683436006,joules
wk2,Dhrystone  - 4 Thread,1,battery_average_power,1.0146634870384617,watts
...
wk3,idle,1,battery_total_energy,0.8461401516782534,joules
wk3,idle,1,battery_average_power,0.045117365812500004,watts

wk1,Dhrystone - 1 Thread,2,battery_total_energy,5.573439491921011,joules
wk1,Dhrystone - 1 Thread,2,battery_average_power,0.18477347599999996,watts
...
wk2,Dhrystone - 4 Thread,2,battery_total_energy,20.866000051942436,joules
wk2,Dhrystone - 4 Thread,2,battery_average_power,0.6890252682000001,watts
...
wk3,idle,2,battery_total_energy,0.8297014357797509,joules
wk3,idle,2,battery_average_power,0.044204215,watts

As can be seen, the results for the same workload appear to vary hugely so it looks like this still requires further digging to see if these readings would be able to produce any useful results.

Please let me know what behavior you observe in your experiments.

[1] https://github.com/ARM-software/workload-automation/pull/1159 [2] PR: https://github.com/ARM-software/devlib/pull/524

qais-yousef commented 3 years ago

Thanks a lot Marc. I was expecting to get current readings. Next week I will dedicate more time to run experiments on Pixel 4 and report back.

marcbonnici commented 3 years ago

Ok great thanks.

Just FYI the raw readings are saved in the job directory to inspect directly, an example is show below with the output normalized using the provided scaling factors.

battery_current,    battery_percent,battery_power,  battery_voltage,    timestamp_time
0.011596,           21.0,       0.043624152,        3.762,      1612262450.199288
0.011596,           21.0,       0.043624152,        3.762,      1612262451.415279
0.964349,           21.0,       3.627880938,        3.762,      1612262452.6282794
0.964349,           21.0,       3.627880938,        3.762,      1612262453.8619392
0.964349,           21.0,       3.627880938,        3.762,      1612262455.0887477
0.964349,           21.0,       3.627880938,        3.762,      1612262456.3295774
...
qais-yousef commented 3 years ago

Sorry for the delayed response. I grabbed your PRs and managed to have a run on Pixel 4 using hackbench. I did 10 runs without a delay between the runs.

hackbench

       battery_current  battery_percent  battery_power  battery_voltage
count        95.000000        95.000000      95.000000        95.000000
mean          0.802582         9.368421      -2.781394         3.469716
std           0.098025         0.799636       0.315172         0.072881
min           0.377187         8.000000      -4.192935         3.336000
25%           0.769843         9.000000      -2.817744         3.426000
50%           0.799375         9.000000      -2.754444         3.438000
75%           0.815937        10.000000      -2.690218         3.481000
max           1.256875        11.000000      -1.364285         3.617000
qais-yousef commented 3 years ago

I think the numbers look sane. I just found 1 second minimum sampling time is too coarse, no?

I did run some experiments with hackbench and reading current_avg from sysfs and the numbers looked sane to me. I used both 10ms and 100ms in that case as I was running a script on the target. Not sure in your case you issue a adb command every 1second or you run something on the target. The latter might be more reliable.

I was talking to the device via TCP by the way.

marcbonnici commented 3 years ago

I just found 1 second minimum sampling time is too coarse, no?

On my device I found that the raw values only updated every 3 or 4 seconds, were you able to obtain more precise data from you device?

I did run some experiments with hackbench and reading current_avg from sysfs and the numbers looked sane to me.

Interesting, do you have a comparisons set of runs using an idle workload to compare the difference?

Not sure in your case you issue a adb command every 1second or you run something on the target. The latter might be more reliable.

Yes the current implementation polls with an adb command which I agree is not ideal, for the proper implementation it would definitely be better to run purely on the device similar to how the file poller operates.

qais-yousef commented 3 years ago

Yes, using my own script I collected data using 10ms and 100ms sampling rate. The 10ms caused the baseline power to be higher since more work is done in the background. This picture is from 5 runs of hackbench with 15 seconds between each run. Note that each run is collected independently and aggregated at the end, hence the slope between each run

pixel4_hackbench

The below is a capture when the device is idle. Note in both tests I hold /sys/power/wake_lock otherwise deep sleep causes issues. I certainly had less variations in the past compared to this run I just captured

pixel4_idle

My script is very simple

#!/bin/sh
set -eu

OUTPUT=$1

echo "timestamp,current" > $OUTPUT
while true
do
    echo "$(date +%s),$(cat /sys/class/power_supply/battery/current_avg)" >> $OUTPUT
    usleep 100000
done
qais-yousef commented 3 years ago

I did these 3 following 1hour runs using 100, 50 and 10 ms sampling rates respectively. Again I hold the wake_lock during these runs.

pixel4_idle_1h_100ms pixel4_idle_1h_50ms pixel4_idle_1h_10ms

marcbonnici commented 3 years ago

Thanks for doing those run, they look a lot more promising than what I had on my device, although I guess that could also be down to the age difference in devices.

Did you have a chance to test out the instrument in the PR's to see if the methodology there is valid and whether that something that would be of use? Or are you looking for something more low level for your own processing? I'm thinking for either approach we could export an attribute of the device to try and auto discover the path of the current node which would allow the use with custom scripts or via the poller instrument for direct access to the counters, and then we'll need a similar attribute for voltage etc.

qais-yousef commented 3 years ago

Did you have a chance to test out the instrument in the PR's to see if the methodology there is valid and whether that something that would be of use? Or are you looking for something more low level for your own processing?

Yes I did. The result in https://github.com/ARM-software/devlib/issues/522#issuecomment-800699045 are from your PRs. I used my own script to see how usable it is to do ms granularity sampling only. If I could have done that with your PR then sorry I missed it.

The goal is really to be able to use that in absence of proper hardware instrumentation. I admit my experience of setting up wa with hardware instrumentation is close to none. But assuming there's a standard output from using acme cape or monsoon for instance, it'd be good if battery_mon produces similar output so that other tools built on top can transparently use it. I'm thinking mainly wltests in LISA.

https://github.com/ARM-software/lisa/tree/master/tools/wltests

Generally with this approach I can think of 2 potential limitations:

  1. Sensitivity. We might not be able to detect a small difference in energy consumption between A/B. By how much more experiments is required.
  2. I think workloads that are relatively busy will be more reliable than the ones that have significant amount of time in idle.

For 2, imagine I have improved the code such that it will allow the device to spend more time in deeper idle state, then the measurement noise could interfere with the ability to detect this.

We might be able to mitigate against that if we ensure the poller script/instrument is attached to "Background" cgroup. This might reduce its impact since they are limited with the amount of resources they can consume.

Thinking out loudly mostly :-)

marcbonnici commented 3 years ago

Yes I did. The result in #522 (comment) are from your PRs. I used my own script to see how usable it is to do ms granularity sampling only. If I could have done that with your PR then sorry I missed it.

Ah right yes that makes sense, no I hadn't expected that granularity to work, so that is something I can modify to allow.

But assuming there's a standard output from using acme cape or monsoon for instance, it'd be good if battery_mon produces similar output so that other tools built on top can transparently use it. I'm thinking mainly wltests in LISA.

We have a standard instrument output format in the form of a MeasurementsCSV which the PR should adhere to and is what allows the DerivedEnergy instrument to operate on the collected output. Although thanks for mentioning wltests, I'm not familiar with what they're expecting so will take a look.

Sensitivity. We might not be able to detect a small difference in energy consumption between A/B. By how much more experiments is required.

Agreed, and by the looks of it, it seems that it would vary largely depending on the particular device in question.

For 2, imagine I have improved the code such that it will allow the device to spend more time in deeper idle state, then the measurement noise could interfere with the ability to detect this.

Yes that is a good point, I think the overhead is going to be the other limitation of this approach, it certainly requires more thought but I guess the first step would be optimize this approach to minimize it's footprint.

qais-yousef commented 3 years ago

I got a pixel 5 setup now too. I had a hackbench run with your PR but the numbers look wrong to me :-/

hackbench

I have tried with my script wtih 100ms sampling rate, it looks okay(ish) but the implementation of /sys/class/power_supply/battery/current_avg is not as good as in pixel 4. The value is only updated at fixed intervals

pixel5_hackbench

Note that on pixel 5 the sysfs returns negative value compared to a positive value on p4. I did all my runs without usb cable plugged, so definitely weren't charging.

On pixel 5 /sys/class/power_supply/bms/current_avg returns an identical value to the one read from 'battery' folder but positive. I hope this makes sense.

qais-yousef commented 3 years ago

In your PR, do you try to use the BatteryService/BatteryManager Java API too or just try to find the best sysfs node to read the values from? Not sure if they're any good by the way, just wondering

marcbonnici commented 3 years ago

In your PR, do you try to use the BatteryService/BatteryManager Java API too or just try to find the best sysfs node to read the values from?

I'm currently just attempting to find the best sysfs node, from my (albeit limited) testing the number reported from both were exactly the same so I thought reading the sysfs node would prevent the requirement for running a separate java application just to call the API. Have you observed any different behavior?

Note that on pixel 5 the sysfs returns negative value compared to a positive value on p4. I did all my runs without usb cable plugged, so definitely weren't charging. On pixel 5 /sys/class/power_supply/bms/current_avg returns an identical value to the one read from 'battery' folder but positive.

That is interesting how is is reported differently on the same device, however to try and combat this as part of the PR when detecting the node it will invert the value as required so that the value is reported (this currently assumes that the device is discharging when initialization takes place however this could be post processed instead if it is more reliable.)

had a hackbench run with your PR but the numbers look wrong to me I have tried with my script wtih 100ms sampling rate, it looks okay(ish) but the implementation of /sys/class/power_supply/battery/current_avg is not as good as in pixel 4. The value is only updated at fixed intervals

Thanks for trying these out, and it is a shame to see that the later pixel phone does not appear to provide as good. Also I might be missing the obvious but which numbers are looking out of place to you for the PR chart vs from your script? The voltage seems about right at 4.xV and the current looks to be similar to your script's output (both unfortunately varying quite a bit more compared to your pixel 4 runs) after it has been inverted to provide a positive value, although it does seem to have a higher average value...

qais-yousef commented 3 years ago

Have you observed any different behavior?

Not not really. And I don't know if it's worth the effort or not to be honest. That API is supposed to go via the HAL, so its implementation will be platform specific. Maybe they just hook it up to the sysfs node.. Who knows. Beside on AOSP builds I don't know if we get proper HAL implementation there either. I doubt it, so it could be a dead end.

Thanks for trying these out, and it is a shame to see that the later pixel phone does not appear to provide as good.

Indeed. Pixel 4 reading the current_avg from sysfs seemed to be decent. But on P5 it's not as good. Potentially I might be able to tinker with the source code to make it behave better..

Also I might be missing the obvious but which numbers are looking out of place to you for the PR chart vs from your script?

Or it could be me who missed something obvious too :-) I got confused because the current values are hovering around 1. But I looked back at the pixel 4 plot in https://github.com/ARM-software/devlib/issues/522#issuecomment-800699045 and it seems you normalize the values and I got caught out with this. So it's me who missed something here hehe :)

Thanks!

qais-yousef commented 3 years ago

Hi. Just checking if there's any plan to handle this request?

marcbonnici commented 3 years ago

Hi @qais-yousef, Apologies I have not had time to make much progress on this since the last update, however yes this is still on my todo list to complete the investigation.

qais-yousef commented 3 years ago

No worries and thanks for the update