1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import threading
import time
from logger.logger import RaptorLogger
LOG = RaptorLogger(component="raptor-cpu")
class AndroidCPUProfiler(object):
"""AndroidCPUProfiler is used to measure CPU usage over time for an
app in testing. Polls usage at a defined interval and then submits this
as a supporting perfherder data measurement.
```
# Initialize the profiler and start polling for CPU usage of the app
cpu_profiler = AndroidCPUProfiler(raptor, poll_interval=1)
cpu_profiler.start_polling()
# Or call the following to perform the commands above
cpu_profiler = start_android_cpu_profiler(raptor)
# Run test...
# Stop measuring and generate perfherder data
cpu_profiler.generate_android_cpu_profile("browsertime-tp6m")
```
"""
def __init__(self, raptor, poll_interval=10):
"""Initialize the Android CPU Profiler.
:param RaptorAndroid raptor: raptor object which contains android device.
:param float poll_interval: interval, in seconds, at which we should poll
for CPU usage. Defaults to 10 seconds.
"""
self.raptor = raptor
self.polls = []
self.poll_interval = poll_interval
self.pause_polling = False
# Get the android version
android_version = self.raptor.device.shell_output(
"getprop ro.build.version.release"
).strip()
self.android_version = int(android_version.split(".")[0])
# Prepare the polling thread (set as daemon for clean exiting)
self.thread = threading.Thread(target=self.poll_cpu, args=(poll_interval,))
self.thread.daemon = True
def start_polling(self):
"""Start the thread responsible for polling CPU usage."""
self.thread.start()
def stop_polling(self):
"""Pauses CPU usage polling."""
self.pause_polling = True
def poll_cpu(self, poll_interval):
"""Polls CPU usage at an interval of `poll_interval`.
:param float poll_interval: interval at which we poll for CPU usage.
"""
while True:
time.sleep(self.poll_interval)
if self.pause_polling:
continue
self.polls.append(self.get_app_cpu_usage())
def get_app_cpu_usage(self):
"""Gather a point on an android app's CPU usage.
:return float: CPU usage found for the app at this point in time.
"""
cpu_usage = 0
app_name = self.raptor.config["binary"]
verbose = self.raptor.device._verbose
self.raptor.device._verbose = False
if self.android_version >= 8:
# On android 8 we can use the -O option to order the entries
# in top by %CPU.
cpuinfo = self.raptor.device.shell_output("top -O %CPU -n 1").split("\n")
for line in cpuinfo:
# A line looks like:
# 14781 u0_a83 0 92.8 12.4 64:53.04 org.mozilla.geckoview_example
data = line.split()
if data[-1] == app_name:
cpu_usage = float(data[3])
else:
# On android 7, -O is not required since top already orders them by %CPU.
# top also has different columns on this android version.
cpuinfo = self.raptor.device.shell_output("top -n 1").split("\n")
# Parse the app-specific entries which look like:
# 21165 u0_a196 10 -10 14% S 67 1442392K 163356K fg org.mozilla.geckoview_example
# Gather the app-specific entries
appcpuinfo = []
for line in cpuinfo:
if not line.strip().endswith(app_name):
continue
appcpuinfo.append(line)
# Get CPU usage
for line in appcpuinfo:
data = line.split()
cpu_usage = float(data[4].strip("%"))
self.raptor.device._verbose = verbose
return cpu_usage
def generate_android_cpu_profile(self, test_name):
"""Use the CPU usage data which was sampled to produce a supporting
perfherder data measurement and submit it to the raptor control
server. Returns the minimum, maximum, and average CPU usage.
:param str test_name: name of the test that was run.
"""
self.stop_polling()
if len(self.polls) == 0:
LOG.info("No CPU usage data found, submitting as 0% usage.")
self.polls.append(0)
avg_cpuinfo_data = {
u"type": u"cpu",
u"test": test_name + "-avg",
u"unit": u"%",
u"values": {
u"avg": sum(self.polls) / len(self.polls)
}, # pylint --py3k W1619
}
self.raptor.control_server.submit_supporting_data(avg_cpuinfo_data)
min_cpuinfo_data = {
u"type": u"cpu",
u"test": test_name + "-min",
u"unit": u"%",
u"values": {u"min": min(self.polls)},
}
self.raptor.control_server.submit_supporting_data(min_cpuinfo_data)
max_cpuinfo_data = {
u"type": u"cpu",
u"test": test_name + "-max",
u"unit": u"%",
u"values": {u"max": max(self.polls)},
}
self.raptor.control_server.submit_supporting_data(max_cpuinfo_data)
def start_android_cpu_profiler(raptor, **kwargs):
"""Start the android CPU profiler and return the profiler
object so measurements can be obtained.
:return AndroidCPUProfiler: the profiler performing the CPU measurements.
"""
if not raptor.device:
LOG.error("No ADB device found in raptor, not creating AndroidCPUProfiler.")
return
cpu_profiler = AndroidCPUProfiler(raptor, **kwargs)
cpu_profiler.start_polling()
return cpu_profiler
|