summaryrefslogtreecommitdiffstats
path: root/testing/raptor/raptor/cpu.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/raptor/raptor/cpu.py')
-rw-r--r--testing/raptor/raptor/cpu.py169
1 files changed, 169 insertions, 0 deletions
diff --git a/testing/raptor/raptor/cpu.py b/testing/raptor/raptor/cpu.py
new file mode 100644
index 0000000000..aa43f06901
--- /dev/null
+++ b/testing/raptor/raptor/cpu.py
@@ -0,0 +1,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