In [1]:
import json
In [2]:
%matplotlib inline
In [3]:
import pandas as pd
Software: Changed code to support perturbing the following values:
Hardware
Workload
In [4]:
raw_data = json.load(open("battery_drain_data.txt"))
In [5]:
raw_df = pd.DataFrame(raw_data)
In [6]:
raw_df[["discharge_screen_off", "name"]].plot(figsize=(18,5), kind="bar", x="name")
Out[6]:
In [7]:
all_day_df = raw_df
all_day_df.discharge_screen_off = raw_df.discharge_screen_off * 4
In [8]:
ax = all_day_df[["discharge_screen_off", "name"]].plot(figsize=(18,5), kind="bar", x="name")
This is consistent with the Martins et al paper in USENIX 2015 (https://www.usenix.org/system/files/conference/atc15/atc15-paper-martins.pdf) where they compared the power drain of a base Android install with one that had Google Play Services installed. Presumably this is what Doze is intended to assist with. My phone is a Galaxy Nexus that is a few generations old, so old that Google no longer releases OTA for it. So I am not running Android Marshmallow and cannot verify what Doze does. It would be pretty cool to see how all this interacts with Doze.
There is one clear anomaly - the low power drain of the ongoing high accuracy collection.
My expectations around high accuracy are as follows:
However, this expectation is not met for the high accuracy case. The ongoing high accuracy case has two anomalies:
This is counter-intuitive. If high accuracy sensing is so expensive, why isn't more of it more expensive? Since this was a counter-intuitive result, I did some sanity checking to ensure that this wasn't a bug.
In [9]:
gps_lock_df = pd.read_csv("gps-lock_data.txt", header=None, names=["ignore1", "ignore2", "ignore3","lock","time","count"])
In [10]:
gps_lock_df[gps_lock_df.ignore2=='t'][["time", "count"]].plot(subplots=True, figsize=(18,10), kind="bar")
Out[10]:
It is pretty clear from the graph above that:
The explanations are not very convincing (if #2 above were true, then the fused API is no longer as compelling), so it would be interesting to explore this further in the future.
In [11]:
6 * 60 * 2
Out[11]:
Note also that we would expect the gps-lock to be acquired 720 times (6 hours, at 30 sec intervals), but it is actually acquired only 350 times. It looks like even in high accuracy mode with the fused API, the OS only turns the GPS on half the time. It either takes two readings per lock, or duty cycles the GPS based on techniques similar to Paek et al from Mobisys 2010. http://dl.acm.org/citation.cfm?id=1814463
Except for the no data collection, I have run the data collection for a particular regime exactly once. This means that some of the other anomalies may just be the result of noise. In particular, there are some small unexpected anomalies in addition to the previous one.
My assumption is that all of these are within the noise for the battery drain, but then are the savings by using the geofence real? I think that we should get multiple runs and get error bars to confirm.
In [22]:
import emission.analysis.plotting.leaflet_osm.ipython_helper as ipy
import emission.analysis.plotting.leaflet_osm.our_plotter as lo
import emission.analysis.plotting.geojson.geojson_feature_converter as gfc
import emission.storage.timeseries.abstract_timeseries as esta
In [24]:
uuids = esta.TimeSeries.get_uuid_list(); uuids
Out[24]:
In [25]:
myuuid = uuids[0]
In [26]:
ts = esta.TimeSeries.get_time_series(myuuid)
In [67]:
import geojson as gj
def getGeojsonPoints(ts, key, start_ts, end_ts):
import emission.net.usercache.abstract_usercache as enua
import emission.core.wrapper.location as ecwl
tq = enua.UserCache.TimeQuery("write_ts", start_ts, end_ts)
entry_it = ts.find_entries([key], tq)
section_location_array = [ecwl.Location(ts._to_df_entry(entry)) for entry in entry_it]
points_feature_array = [gfc.location_to_geojson(l) for l in section_location_array]
print ("Found %d points" % len(points_feature_array))
points_line_string = gj.LineString()
points_line_string.coordinates = []
for loc in section_location_array:
points_line_string.coordinates.append(l.loc.coordinates)
points_line_feature = gj.Feature()
points_line_feature.geometry = points_line_string
if len(points_feature_array) != 0:
feature_array = []
feature_array.append(points_feature_array[0])
feature_array.append(points_feature_array[1])
feature_array.append(gj.FeatureCollection(points_feature_array))
feature_array.append(points_line_feature)
feature_coll = gj.FeatureCollection(feature_array)
else:
feature_coll = gj.FeatureCollection([])
return feature_coll
In [70]:
# geofence balanced power 1 min
geofence_balanced_power_1_min = getGeojsonPoints(ts, "background/filtered_location", 1445450400, 1445472000)
geofence_balanced_power_2_sec = getGeojsonPoints(ts, "background/filtered_location", 1445427600, 1445449200)
geofence_balanced_power_30_sec = getGeojsonPoints(ts, "background/filtered_location", 1445353200, 1445374800)
# geofence_high_accuracy_30_sec = getGeojsonPoints(ts, "background/filtered_location", 1445544000, 1445567400)
In [77]:
map_list = lo.get_maps_for_geojson_list([geofence_balanced_power_1_min, geofence_balanced_power_30_sec, geofence_balanced_power_2_sec])
In [79]:
ipy.inline_maps([map_list])
Out[79]:
In [53]:
import datetime as pydt
pydt.datetime.fromtimestamp(1448031600)
Out[53]:
In [42]:
edb.get_timeseries_db().find_one()
Out[42]:
In order to generate these stats, I used the adb shell dumpsys batteryinfo command. This command is replaced in later versions of android with the adb shell dumpsys batterystats command and the current documentation (https://source.android.com/devices/tech/power/batterystats.html) refers to the output of the current command.
In the prior analysis, I have primarily used the battery drain, which can be measured directly using a standard API. However, the dumpsys shell command promises to provide additional information which could potentially be used to build a richer model. How much additional information can I collect, and how useful is it?
My first thought was to use
$ adb shell dumpsys batterystats --charged batteryinfo.
The output also does not support the pwi (Power Use Item) or pws (Power Use Summary) fields.
I then looked at the raw data and pulled out some information that appeared to be relevant.
cfc_tracker or mobility)#10065:
Network: 36.45MB received, 860.01KB sent
Wake lock GCoreFlp: 1s 223ms partial (273 times) realtime
Wake lock GeofencePendingIntentWakeLock: 4ms partial (2 times) realtime
Wake lock *sync*_edu.berkeley.eecs.cfc_tracker.provider_Account {name=dummy_account, type=openbms.org}: 5m 25s 57ms partial (10 times) realtime
TOTAL wake: 5m 26s 284ms partial realtime
Proc *wakelock*:
CPU: 58s 910ms usr + 15s 270ms krn
Proc edu.berkeley.eecs.cfc_tracker:sync:
CPU: 1m 35s 600ms usr + 6s 500ms krn
10 proc starts
Proc edu.berkeley.eecs.cfc_tracker:
CPU: 16s 610ms usr + 5s 540ms krn
8 proc starts
Apk edu.berkeley.eecs.cfc_tracker:
Service edu.berkeley.eecs.cfc_tracker.location.TripDiaryStateMachineService:
Created for: 3s 601ms uptime
Starts: 4, launches: 4
Service edu.berkeley.eecs.cfc_tracker.location.GeofenceExitIntentService:
Created for: 1s 452ms uptime
Starts: 2, launches: 2
Service edu.berkeley.eecs.cfc_tracker.smap.SyncService:
Created for: 0ms uptime
Starts: 0, launches: 10
Service edu.berkeley.eecs.cfc_tracker.location.ActivityRecognitionChangeIntentService:
Created for: 18s 61ms uptime
Starts: 37, launches: 37
Service edu.berkeley.eecs.cfc_tracker.location.LocationChangeIntentService:
Created for: 1m 54s 703ms uptime
Starts: 94, launches: 94
Get additional set of readings for the following regimes so that we can get some error bars.
We do not need to repeat the 2 sec interval readings since the results were unambiguous in that case.
Since I had only a week between the previous result and the current presentation, I was able to get error bars for only some of the regimes. I got the most data for the "no data collection" or baseline regime, since it showed a fair amount of variability, and it is key to know how much additional drain continuous collection adds.
In [32]:
no_data_collection_df = pd.read_csv("no_data_collection_error_bars.txt", header=None, names=["ignore1", "tu", "ignore3", "low","high","on","off"])
geofence_collection_df = pd.read_csv("geofence_100m_plus_fused_balanced_power_30sec_error_bars.txt", header=None, names=["ignore1", "tu", "ignore3", "low","high","on","off"])
ongoing_collection_df = pd.read_csv("ongoing_plus_fused_balanced_power_30sec_error_bars.txt", header=None, names=["ignore1", "tu", "ignore3", "low","high","on","off"])
In [33]:
no_data_collection_df[no_data_collection_df.tu=='t'][["off"]].plot(figsize=(18,6), kind="bar")
Out[33]:
In [34]:
geofence_collection_df[geofence_collection_df.tu=='t'][["off"]].plot(figsize=(18,6), kind="bar")
Out[34]:
In [35]:
ongoing_collection_df[ongoing_collection_df.tu=='t'][["off"]].plot(figsize=(18,6), kind="bar")
Out[35]:
In [36]:
import datetime as pydt
pydt.datetime.fromtimestamp(1446068421)
Out[36]:
In [37]:
no_data_collection_df[no_data_collection_df.tu=='t'].off.describe()
Out[37]:
In [38]:
geofence_collection_df[geofence_collection_df.tu=='t'].off.describe()
Out[38]:
In [39]:
ongoing_collection_df[ongoing_collection_df.tu=='t'].off.describe()
Out[39]:
In [17]:
raw_df.plot(subplots=True, figsize=(20,100), kind="bar", x="name")
Out[17]:
In [ ]: