266 lines
9.7 KiB
Python
266 lines
9.7 KiB
Python
# 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 os
|
|
import pathlib
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
from mozperftest.utils import ON_TRY
|
|
|
|
# Add the python packages installed by mozperftest
|
|
sys.path.insert(0, os.environ["PYTHON_PACKAGES"])
|
|
|
|
import cv2
|
|
import numpy as np
|
|
from mozdevice import ADBDevice
|
|
from mozperftest.profiler import ProfilingMediator
|
|
|
|
PROD_FENIX = "fenix"
|
|
PROD_CHRM = "chrome-m"
|
|
BACKGROUND_TABS = [
|
|
"https://www.google.com/search?q=toronto+weather",
|
|
"https://en.m.wikipedia.org/wiki/Anemone_hepatica",
|
|
"https://www.amazon.ca/gp/aw/gb?ref_=navm_cs_gb&discounts-widget",
|
|
"https://www.espn.com/nfl/game/_/gameId/401671793/chiefs-falcons",
|
|
]
|
|
ITERATIONS = 5
|
|
|
|
|
|
class ImageAnalzer:
|
|
def __init__(self, browser, test, test_url):
|
|
self.video = None
|
|
self.browser = browser
|
|
self.test = test
|
|
self.test_url = test_url
|
|
self.width = 0
|
|
self.height = 0
|
|
self.video_name = ""
|
|
self.package_name = os.environ["BROWSER_BINARY"]
|
|
self.device = ADBDevice()
|
|
self.profiler = ProfilingMediator()
|
|
self.cpu_data = {"total": {"time": []}}
|
|
if self.browser == PROD_FENIX:
|
|
self.intent = "org.mozilla.fenix/org.mozilla.fenix.IntentReceiverActivity"
|
|
elif self.browser == PROD_CHRM:
|
|
self.intent = (
|
|
"com.android.chrome/com.google.android.apps.chrome.IntentDispatcher"
|
|
)
|
|
else:
|
|
raise Exception("Bad browser name")
|
|
self.nav_start_command = (
|
|
f"am start-activity -W -n {self.intent} -a "
|
|
f"android.intent.action.VIEW -d "
|
|
)
|
|
self.view_intent_command = (
|
|
f"am start-activity -W -n {self.intent} -a " f"android.intent.action.VIEW"
|
|
)
|
|
|
|
self.device.shell("mkdir -p /sdcard/Download")
|
|
self.device.shell("settings put global window_animation_scale 1")
|
|
self.device.shell("settings put global transition_animation_scale 1")
|
|
self.device.shell("settings put global animator_duration_scale 1")
|
|
|
|
def app_setup(self):
|
|
if ON_TRY:
|
|
self.device.shell(f"pm clear {self.package_name}")
|
|
time.sleep(3)
|
|
self.skip_onboarding()
|
|
self.device.shell(
|
|
f"pm grant {self.package_name} android.permission.POST_NOTIFICATIONS"
|
|
) # enabling notifications
|
|
if self.test != "homeview_startup":
|
|
self.create_background_tabs()
|
|
self.device.shell(f"am force-stop {self.package_name}")
|
|
|
|
def skip_onboarding(self):
|
|
# Skip onboarding for chrome and fenix
|
|
if self.browser == PROD_CHRM:
|
|
self.device.shell_output(
|
|
'echo "chrome --no-default-browser-check --no-first-run '
|
|
'--disable-fre" > /data/local/tmp/chrome-command-line '
|
|
)
|
|
self.device.shell("am set-debug-app --persistent com.android.chrome")
|
|
elif self.browser == PROD_FENIX:
|
|
self.device.shell(
|
|
"am start-activity -W -a android.intent.action.MAIN --ez "
|
|
"performancetest true -n org.mozilla.fenix/org.mozilla.fenix.App"
|
|
)
|
|
|
|
def create_background_tabs(self):
|
|
# Add background tabs that allow us to see the impact of having background tabs open
|
|
# when we do the cold applink startup test. This makes the test workload more realistic
|
|
# and will also help catch regressions that affect per-open-tab startup work.
|
|
for website in BACKGROUND_TABS:
|
|
self.device.shell(self.nav_start_command + website)
|
|
time.sleep(3)
|
|
if self.test == "mobile_restore":
|
|
self.load_page_to_test_startup()
|
|
|
|
def get_video(self, run):
|
|
self.video_name = f"vid{run}_{self.browser}.mp4"
|
|
video_location = f"/sdcard/Download/{self.video_name}"
|
|
|
|
# Bug 1927548 - Recording command doesn't use mozdevice shell because the mozdevice shell
|
|
# outputs an adbprocess obj whose adbprocess.proc.kill() does not work when called
|
|
recording = subprocess.Popen(
|
|
[
|
|
"adb",
|
|
"shell",
|
|
"screenrecord",
|
|
"--bugreport",
|
|
video_location,
|
|
]
|
|
)
|
|
|
|
# Start Profilers if enabled.
|
|
self.profiler.start()
|
|
|
|
if self.test == "cold_view_nav_end":
|
|
self.load_page_to_test_startup()
|
|
elif self.test in ["mobile_restore", "homeview_startup"]:
|
|
self.open_browser_with_view_intent()
|
|
|
|
# Stop Profilers if enabled.
|
|
self.profiler.stop(os.environ["TESTING_DIR"], run)
|
|
|
|
self.process_cpu_info(run)
|
|
recording.kill()
|
|
time.sleep(5)
|
|
self.device.command_output(
|
|
["pull", "-a", video_location, os.environ["TESTING_DIR"]]
|
|
)
|
|
|
|
time.sleep(4)
|
|
video_location = pathlib.Path(os.environ["TESTING_DIR"], self.video_name)
|
|
|
|
self.video = cv2.VideoCapture(video_location)
|
|
self.width = self.video.get(cv2.CAP_PROP_FRAME_WIDTH)
|
|
self.height = self.video.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
|
self.device.shell(f"am force-stop {self.package_name}")
|
|
|
|
def get_image(self, frame_position):
|
|
self.video.set(cv2.CAP_PROP_POS_FRAMES, frame_position)
|
|
ret, frame = self.video.read()
|
|
if not ret:
|
|
raise Exception("Frame not read")
|
|
# We crop out the top 100 pixels in each image as when we have --bug-report in the
|
|
# screen-recording command it displays a timestamp which interferes with the image comparisons
|
|
return frame[100 : int(self.height), 0 : int(self.width)]
|
|
|
|
def error(self, img1, img2):
|
|
h = img1.shape[0]
|
|
w = img1.shape[1]
|
|
diff = cv2.subtract(img1, img2)
|
|
err = np.sum(diff**2)
|
|
mse = err / (float(h * w))
|
|
return mse
|
|
|
|
def get_page_loaded_time(self):
|
|
"""
|
|
Returns the index of the frame where the main image on the shopify demo page is displayed
|
|
for the first time.
|
|
Specifically, we find the index of the first frame whose image is within an error of 20
|
|
compared to the final frame, via binary search. The binary search assumes that the error
|
|
compared to the final frame decreases monotonically in the captured frames.
|
|
"""
|
|
final_frame_index = self.video.get(cv2.CAP_PROP_FRAME_COUNT) - 1
|
|
final_frame = self.get_image(final_frame_index)
|
|
|
|
lo = 0
|
|
hi = final_frame_index
|
|
|
|
while lo < hi:
|
|
mid = (lo + hi) // 2
|
|
diff = self.error(self.get_image(mid), final_frame)
|
|
if diff <= 20:
|
|
hi = mid
|
|
else:
|
|
lo = mid + 1
|
|
return lo
|
|
|
|
def get_time_from_frame_num(self, frame_num):
|
|
self.video.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
|
|
self.video.read()
|
|
return self.video.get(cv2.CAP_PROP_POS_MSEC)
|
|
|
|
def load_page_to_test_startup(self):
|
|
# Navigate to the page we want to use for testing startup
|
|
self.device.shell(self.nav_start_command + self.test_url)
|
|
time.sleep(5)
|
|
|
|
def open_browser_with_view_intent(self):
|
|
self.device.shell(self.view_intent_command)
|
|
time.sleep(5)
|
|
|
|
def process_cpu_info(self, run):
|
|
cpu_info = self.device.shell_output(
|
|
f"ps -A -o name=,cpu=,time+=,%cpu= | grep {self.package_name}"
|
|
).split("\n")
|
|
total_time_seconds = tab_processes_time = 0
|
|
for process in cpu_info:
|
|
process_name = re.search(r"([\w\d_.:]+)\s", process).group(1)
|
|
time = re.search(r"\s(\d+):(\d+).(\d+)\s", process)
|
|
time_seconds = (
|
|
10 * int(time.group(3))
|
|
+ 1000 * int(time.group(2))
|
|
+ 60 * 1000 * int(time.group(1))
|
|
)
|
|
total_time_seconds += time_seconds
|
|
if "org.mozilla.fenix:tab" in process_name:
|
|
process_name = "org.mozilla.fenix:tab"
|
|
|
|
if process_name not in self.cpu_data.keys():
|
|
self.cpu_data[process_name] = {}
|
|
self.cpu_data[process_name]["time"] = []
|
|
|
|
if "org.mozilla.fenix:tab" == process_name:
|
|
tab_processes_time += time_seconds
|
|
continue
|
|
self.cpu_data[process_name]["time"] += [time_seconds]
|
|
|
|
if tab_processes_time:
|
|
self.cpu_data["org.mozilla.fenix:tab"]["time"] += [tab_processes_time]
|
|
self.cpu_data["total"]["time"] += [total_time_seconds]
|
|
|
|
def perfmetrics_cpu_data_ingesting(self):
|
|
for process in self.cpu_data.keys():
|
|
print(
|
|
'perfMetrics: {"values": '
|
|
+ str(self.cpu_data[process]["time"])
|
|
+ ', "name": "'
|
|
+ process
|
|
+ '-cpu-time", "shouldAlert": true }'
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) != 4:
|
|
raise Exception("Didn't pass the args properly :(")
|
|
start_video_timestamp = []
|
|
browser = sys.argv[1]
|
|
test = sys.argv[2]
|
|
test_url = sys.argv[3]
|
|
|
|
perfherder_names = {
|
|
"cold_view_nav_end": "applink_startup",
|
|
"mobile_restore": "tab_restore",
|
|
"homeview_startup": "homeview_startup",
|
|
}
|
|
|
|
ImageObject = ImageAnalzer(browser, test, test_url)
|
|
for iteration in range(ITERATIONS):
|
|
ImageObject.app_setup()
|
|
ImageObject.get_video(iteration)
|
|
nav_done_frame = ImageObject.get_page_loaded_time()
|
|
start_video_timestamp += [ImageObject.get_time_from_frame_num(nav_done_frame)]
|
|
print(
|
|
'perfMetrics: {"values": '
|
|
+ str(start_video_timestamp)
|
|
+ ', "name": "'
|
|
+ perfherder_names[test]
|
|
+ '", "shouldAlert": true}'
|
|
)
|
|
ImageObject.perfmetrics_cpu_data_ingesting()
|