Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detailed battery usage - iOS

My current project is a constant-presence application (think Tinder or Foursquare), and the battery consumption is through the roof. We think the main draw on power are the GPS and WiFi antennas. We'd like to be able to measure our app's energy usage under several different configurations.

But how to do this? We want a process that:

  • Can be used with the phone disconnected from the computer (so we know we're using battery rather than drawing power over USB),
  • Has sufficient granularity to allow us to correlate energy spikes to application events (launching the app, updating the location, sending analytics information to Mixpanel, etc),
  • Can be run overnight without babysitting,
  • Can be exported as CSV or whatever for statistical analysis.

Quite the list, I know.

Those are the requirements, and here are the options that I'm aware of:

1. Turning on untethered Energy Diagnostics Logging on an iOS device, and exporting to Instruments

This is the obvious answer, but it has one gigantic flaw.

Pros:

  • Uses battery instead of USB power.
  • Excellent granularity (1sec time series data, 20 discrete energy usage levels),
  • Correlated to other device events like GPS antenna usage etc.

Cons:

  • Can't run overnight because "if the device battery runs dry or the iOS Device is powered off, the log data is lost." This is a CRAZY restriction, and pretty much makes this approach useless for our case (https://developer.apple.com/library/ios/recipes/Instruments_help_articles/LoggingEnergyUsageinaniOSDevice/LoggingEnergyUsageinaniOSDevice.html).
  • Can't export to CSV. Because Instruments.

2. Monitoring a plugged-in phone through Instruments

Pros:

  • Same excellent granularity and correlation to other device events.
  • Battery can't run out.

Cons:

  • Doesn't use the battery, so the energy consumption is incomparable to real-world usage.
  • Instruments doesn't even reliably show energy usage. Sometimes it's just blank.
  • Can't export to CSV.

3. Using public Cocoa APIs to record energy usage in our app – [UIDevice.currentDevice batteryLevel]

This is the most common answer on SO. I've looked at Estimated battery time on iOS, iphone: Calculating battery life and about a dozen others.

Pros:

  • Arbitrarily small time between measurements.
  • Can persist data even if the battery dies by writing to disk somehow (CoreData, defaults, network, whatever).
  • Can choose arbitrary format for the data, say CSV.

Cons:

  • A lot more work than the other approaches.
  • Public API only gives you battery level to 5% accuracy. This is essentially the time integral of the instantaneous power consumption data we get from approaches 1 and 2. Not nearly granular enough to correlate with other device events (but maybe enough to get a gross estimate for device battery life).

4. Using private Cocoa APIs to record energy usage

As we'll only ever be doing this during development, it doesn't matter if Apple would reject the app for using a private API. Presumably there's some private API for this, as Apple is able to record the data with Untethered Energy Diagnostics turned on.

Pros:

  • Arbitrarily granularity, arbitrary file format, arbitrary persistence.

Cons:

  • Waaaaay more work to figure out how to use it. Maybe not even possible.

5. Combined approach

We could use the untethered diagnostics to quantify the marginal energy cost for each action. "Ok, spinning up the GPS antenna takes 150mW•H. Calculating position takes 50mW•H. Sending a Mixpanel event takes 25mW•H, unless we made another network call within the previous 30sec, in which case it takes 15mW•H." (all numbers invented on the spot.) Then we can use the tethered monitoring to record when each of those actions takes place, plug into a linear equation, and estimate the amount of energy it should have taken.

Pros:

  • Flexible. Arbitrary everything.

Cons:

  • Maths.
  • Very easy to miss non-linear contributions. Getting something like E = k0/(gps poll interval) + k1*(number of analytics calls) is easy, but what if the cost of spinning up both antennas is less than the sum of spinning them each up separately?
  • Ignores any caching strategies that Apple might make internally in Location Services.
  • Not even remotely close to real-world utilization.

Anyhow, I've blathered enough. Has anybody done this before? How?

like image 332
Michael Chinigo Avatar asked Jul 18 '14 18:07

Michael Chinigo


People also ask

How do I hide battery usage activity on iPhone?

Although you cannot reset battery usage on iPhone, it is possible to hide app battery usage. Go to the Settings app from your home screen, choose General, Usage, and Battery Usage. Then decide to display or hide battery usage for each app.

How do I see battery usage on Apple device?

Just swipe down from the top-right corner of your display. With iOS 16, you can turn on the battery percentage so it appears in your status bar. Go to Settings > Battery, and turn on Battery Percentage.


1 Answers

Not sure if this is applicable to this exact use-case, but I've developed a library called UIDeviceListener that allows you to easily (basically with one line of code), retrieve data from the operating system regarding power consumption and many other battery/charging related data points: https://github.com/eldoogy/PowerData

Here is a sample dictionary to give you an idea of the kind of information you can get. I will point your attention to the InstantAmperage key. This shows real-time power consumption in mA for the entire device (while the device is unplugged). That might help accomplish what you're looking for here.

{
AdapterDetails =     {
    Amperage = 1000;
    Description = "usb host";
    FamilyCode = "-536854528";
    PMUConfiguration = 1000;
    Watts = 5;
};
AdapterInfo = 16384;
Amperage = 1000;
AppleRawCurrentCapacity = 1279;
AppleRawMaxCapacity = 1275;
AtCriticalLevel = 0;
AtWarnLevel = 0;
BatteryData =     {
    BatterySerialNumber = REDACTED;
    ChemID = 355;
    CycleCount = 524;
    DesignCapacity = 1420;
    Flags = 640;
    FullAvailableCapacity = 1325;
    ManufactureDate = REDACTED;
    MaxCapacity = 1273;
    MfgData = REDACTED;
    QmaxCell0 = 1350;
    StateOfCharge = 100;
    Voltage = 4194;
};
BatteryInstalled = 1;
BatteryKey = "0003-default";
BootBBCapacity = 52;
BootCapacityEstimate = 2;
BootVoltage = 3518;
CFBundleIdentifier = "com.apple.driver.AppleD1815PMU";
ChargerConfiguration = 990;
CurrentCapacity = 1275;
CycleCount = 524;
DesignCapacity = 1420;
ExternalChargeCapable = 1;
ExternalConnected = 1;
FullyCharged = 1;
IOClass = AppleD1815PMUPowerSource;
IOFunctionParent64000000 = <>;
IOGeneralInterest = "IOCommand is not serializable";
IOInterruptControllers =     (
    IOInterruptController34000000,
    IOInterruptController34000000,
    IOInterruptController34000000,
    IOInterruptController34000000
);
IOInterruptSpecifiers =     (
    <03000000>,
    <26000000>,
    <04000000>,
    <24000000>
);
IOMatchCategory = AppleD1815PMUPowerSource;
IOPowerManagement =     {
    CurrentPowerState = 2;
    DevicePowerState = 2;
    MaxPowerState = 2;
};
IOProbeScore = 0;
IOProviderClass = AppleD1815PMU;
InstantAmperage = 0;
IsCharging = 0;
Location = 0;
Manufacturer = A;
MaxCapacity = 1275;
Model = "0003-A";
Serial = REDACTED;
Temperature = 2590;
TimeRemaining = 0;
UpdateTime = 1461830702;
Voltage = 4182;
"battery-data" =     {
    "0003-default" = <...>;
    "0004-default" = <...>;
    "0005-default" = <...};
"built-in" = 1;
}

UIDeviceListener supports regular, non jailbroken iOS devices and does not invoke any private APIs.

like image 63
ldoogy Avatar answered Oct 11 '22 16:10

ldoogy