Permalink
Browse files

Preliminary support for writing sps profiles during capture

  • Loading branch information...
1 parent e1c44f2 commit 22b5587879d595ee8e7329780b8fff7e194ab31c Benoit Girard committed with wlach Aug 30, 2012
View
3 .gitmodules
@@ -25,3 +25,6 @@
[submodule "src/marionette_client"]
path = src/marionette_client
url = git://github.com/mozilla/marionette_client
+[submodule "src/GeckoProfilerAddon"]
+ path = src/GeckoProfilerAddon
+ url = https://github.com/bgirard/Gecko-Profiler-Addon.git
View
16 bin/get-metric-for-build.py
@@ -57,7 +57,7 @@ def get_build_for_date(date):
def run_test(device, outputdir, outputfile, test, url_params, num_runs,
startup_test, no_capture, get_internal_checkerboard_stats, apk=None,
- appname = None, appdate = None):
+ appname = None, appdate = None, profile_file=None):
if apk:
appinfo = eideticker.get_fennec_appinfo(apk)
appname = appinfo['appname']
@@ -88,6 +88,8 @@ def run_test(device, outputdir, outputfile, test, url_params, num_runs,
args.extend(["--no-capture"])
else:
args.extend(["--capture-file", capture_file])
+ if profile_file:
+ args.extend(["--profile-file", profile_file])
print args
retval = subprocess.call(args)
if retval != 0:
@@ -183,6 +185,9 @@ def main(args=sys.argv[1:]):
dest = "no_capture",
help = "run through the test, but don't actually "
"capture anything")
+ parser.add_option("--profile-file", action="store",
+ type="string", dest = "profile_file",
+ help="Collect a performance profile using the built in profiler.")
parser.add_option("--get-internal-checkerboard-stats",
action="store_true",
dest="get_internal_checkerboard_stats",
@@ -247,7 +252,8 @@ def main(args=sys.argv[1:]):
options.num_runs,
options.startup_test,
options.no_capture,
- options.get_internal_checkerboard_stats, appname=appname)
+ options.get_internal_checkerboard_stats, appname=appname,
+ profile_file=options.profile_file)
elif apks:
for apk in apks:
run_test(device, options.outputdir,
@@ -256,7 +262,8 @@ def main(args=sys.argv[1:]):
options.num_runs,
options.startup_test,
options.no_capture,
- options.get_internal_checkerboard_stats, apk=apk)
+ options.get_internal_checkerboard_stats, apk=apk,
+ profile_file=options.profile_file)
else:
for date in dates:
apk = get_build_for_date(date)
@@ -267,7 +274,8 @@ def main(args=sys.argv[1:]):
options.startup_test,
options.no_capture,
options.get_internal_checkerboard_stats, apk=apk,
- appdate=date)
+ appdate=date,
+ profile_file=options.profile_file)
main()
View
5 bin/runtest.py
@@ -126,6 +126,9 @@ def main(args=sys.argv[1:]):
parser.add_option("--b2g", action="store_true",
dest="b2g", default=False,
help="Run in B2G environment. You do not need to pass an appname")
+ parser.add_option("--profile-file", action="store",
+ type="string", dest = "profile_file",
+ help="Collect a performance profile using the built in profiler.")
options, args = parser.parse_args()
testpath, appname = None, None
@@ -259,7 +262,7 @@ def main(args=sys.argv[1:]):
if options.startup_test and not options.no_capture:
capture_controller.start_capture(capture_file, device.hdmiResolution,
capture_metadata)
- runner.start()
+ runner.start(profile_file=options.profile_file)
# Keep on capturing until we timeout
if capture_timeout:
View
23 bin/update-dashboard.py
@@ -69,13 +69,27 @@ def kill_app(dm, appname):
if name == appname:
dm.runCmd(["shell", "echo kill %s | su" % pid])
+def symbolicate_profile_package(profile_package, profile_path, profile_file)
+ retval = subprocess.call(["./symbolicate.sh",
+ os.path.abspath(profile_package), os.path.abspath(profile_file)],
+ cwd="../src/GeckoProfilerAddon")
+ if retval == 0:
+ return profile_path
+ else:
+ return None
+
def runtest(dm, product, current_date, appname, appinfo, test, capture_name,
outputdir, datafile, data):
capture_file = os.path.join(CAPTURE_DIR,
"%s-%s-%s-%s.zip" % (test['name'],
appname,
appinfo.get('date'),
int(time.time())))
+ profile_package = os.path.join(CAPTURE_DIR,
+ "profile-package-%s-%s-%s-%s.zip" % (test['name'],
+ appname,
+ appinfo.get('date'),
+ int(time.time())))
urlparams = test.get('urlparams', '')
test_completed = False
@@ -85,6 +99,7 @@ def runtest(dm, product, current_date, appname, appinfo, test, capture_name,
retval = subprocess.call(["runtest.py", "--url-params", urlparams,
"--name", capture_name,
"--capture-file", capture_file,
+ "--profile-file", profile_package,
appname, test['path']])
if retval == 0:
test_completed = True
@@ -104,6 +119,13 @@ def runtest(dm, product, current_date, appname, appinfo, test, capture_name,
video_file = os.path.join(outputdir, video_path)
open(video_file, 'w').write(capture.get_video().read())
+ # profile file
+ if profile_package:
+ profile_path = os.path.join('profiles', 'sps-profile-%s.zip' % time.time())
+ profile_file = os.path.join(outputdir, profile_path)
+ profile_path = symbolicate_profile_package(profile_package, profile_path, profile_file)
+ os.remove(profile_package)
+
# frames-per-second / num unique frames
num_unique_frames = videocapture.get_num_unique_frames(capture)
fps = videocapture.get_fps(capture)
@@ -122,6 +144,7 @@ def runtest(dm, product, current_date, appname, appinfo, test, capture_name,
'checkerboard': checkerboard,
'uniqueframes': num_unique_frames,
'video': video_path,
+ 'profile': profile_path,
'appdate': appinfo.get('date'),
'buildid': appinfo.get('buildid'),
'revision': appinfo.get('revision') }
1 src/GeckoProfilerAddon
@@ -0,0 +1 @@
+Subproject commit 96f879f84a2b29364f3171ce0b9c4d28c39c7035
View
14 src/eideticker/eideticker/device.py
@@ -83,6 +83,20 @@ def _executeScript(self, events, executeCallback=None):
self._shellCheckOutput(["/system/xbin/orng", self.inputDevice,
remotefilename])
+ def getPIDs(self, appname):
+ '''FIXME: Total hack, put this in devicemanagerADB instead'''
+ procs = self.getProcessList()
+ pids = []
+ for (pid, name, user) in procs:
+ if name == appname:
+ pids.append(pid)
+ return pids
+
+ def sendSaveProfileSignal(self, appName):
+ pids = self.getPIDs(appName)
+ if pids:
+ self._shellCheckOutput(['kill', '-s', '12', pids[0]])
+
def getprop(self, prop):
return self._shellCheckOutput(["getprop", str(prop)])
View
120 src/eideticker/eideticker/runner.py
@@ -5,10 +5,12 @@
import datetime
import mozprofile
import os
+import tempfile
import time
import socket
import subprocess
import sys
+import zipfile
from marionette import Marionette
@@ -120,7 +122,101 @@ def __init__(self, dm, appname, url):
else:
self.activity = activity_mappings[self.appname]
- def start(self):
+ def get_profile(self, target_file):
+ if self.is_profiling == False:
+ raise Exception("Can't get profile if it isn't started with the profiling option")
+
+ files_to_package = []
+
+ # create a temporary directory to place the profile and shared libraries
+ tmpdir = tempfile.mkdtemp()
+
+ # remove previous profiles if there is one
+ profile_path = os.path.join(tmpdir, "fennec_profile.txt")
+ if os.path.exists(profile_path):
+ os.remove(profile_path)
+
+ print "Fetching fennec_profile.txt"
+ self.dm.getFile(self.profile_location, profile_path)
+ files_to_package.append(profile_path);
+
+ with zipfile.ZipFile(target_file, "w") as zip_file:
+ for file_to_package in files_to_package:
+ print "File to zip: " + file_to_package
+ zip_file.write(file_to_package, os.path.basename(file_to_package))
+
+ def get_profile_and_symbols(self, target_zip):
+ if self.is_profiling == False:
+ raise Exception("Can't get profile if it isn't started with the profiling option")
+
+ files_to_package = []
+
+ # create a temporary directory to place the profile and shared libraries
+ tmpdir = tempfile.mkdtemp()
+
+ # remove previous profiles if there is one
+ profile_path = os.path.join(tmpdir, "fennec_profile.txt")
+ if os.path.exists(profile_path):
+ os.remove(profile_path)
+
+ print "Fetching fennec_profile.txt"
+ self.dm.getFile(self.profile_location, profile_path)
+ files_to_package.append(profile_path);
+
+ print "Fetching app symbols"
+ apk_path = os.path.join(tmpdir, "symbol.apk")
+ try:
+ result = self.dm.getFile('/data/app/' + self.appname + '-1.apk', apk_path)
+ if result != None:
+ files_to_package.append(apk_path);
+ else:
+ # This is nasty repetition. We can either return a None result
+ # or throw subprocess.CalledProcessError.
+ try:
+ result = self.dm.getFile('/data/app/' + self.appname + '-2.apk', apk_path)
+ if result != None:
+ files_to_package.append(apk_path);
+ else:
+ print "Warning: could not get the apk"
+ except subprocess.CalledProcessError:
+ pass # We still get a useful profile without the symbols from the apk
+ except subprocess.CalledProcessError:
+ try:
+ self.dm.getFile('/data/app/' + self.appname + '-2.apk', apk_path)
+ files_to_package.append(apk_path);
+ except subprocess.CalledProcessError:
+ pass # We still get a useful profile without the symbols from the apk
+
+ # get all the symbols library for symbolication
+ print "Fetching system libraries"
+ libpaths = [ "/system/lib",
+ "/system/lib/egl",
+ "/system/lib/hw",
+ "/system/vendor/lib",
+ "/system/vendor/lib/egl",
+ "/system/vendor/lib/hw",
+ "/system/b2g" ]
+
+ for libpath in libpaths:
+ print "Fetching from: " + libpath
+ dirlist = self.dm.listFiles(libpath)
+ for filename in dirlist:
+ filename = filename.strip()
+ if filename.endswith(".so"):
+ try:
+ lib_path = os.path.join(tmpdir, filename)
+ results = self.dm.getFile(libpath + '/' + filename, lib_path)
+ if results != None:
+ files_to_package.append(lib_path);
+ except subprocess.CalledProcessError:
+ print "failed to fetch: " + fileName
+
+ with zipfile.ZipFile(target_zip, "w") as zip_file:
+ for file_to_package in files_to_package:
+ print "File to zip: " + file_to_package
+ zip_file.write(file_to_package, os.path.basename(file_to_package))
+
+ def start(self, profile_file=None):
print "Starting %s... " % self.appname
# for fennec only, we create and use a profile
@@ -135,19 +231,39 @@ def start(self):
if not self.dm.pushDir(profile.profile, self.remote_profile_dir):
raise Exception("Failed to copy profile to device")
+ self.is_profiling = profile_file != None
+ if self.is_profiling:
+ self.profile_file = profile_file
+ mozEnv = { "MOZ_PROFILER_STARTUP": "true" }
+ else:
+ mozEnv = None
+
args.extend(["-profile", self.remote_profile_dir])
# sometimes fennec fails to start, so we'll try three times...
for i in range(3):
print "Launching fennec (try %s of 3)" % (i+1)
- if self.dm.launchFennec(self.appname, url=self.url, extraArgs=args):
+ if self.dm.launchFennec(self.appname, url=self.url, mozEnv=mozEnv, extraArgs=args):
return
raise Exception("Failed to start Fennec after three tries")
else:
self.dm.launchApplication(self.appname, self.activity, self.intent,
url=self.url)
def stop(self):
+ # Dump the profile
+ if self.is_profiling:
+ print "Saving sps performance profile"
+ self.dm.sendSaveProfileSignal(self.appname)
+ self.profile_location = "/mnt/sdcard/profile_0_%s.txt" % self.dm.getPIDs(self.appname)[0]
+ # Saving goes through the main event loop so give it time to flush
+ time.sleep(10)
+
self.dm.killProcess(self.appname)
+
+ # Process the profile
+ if self.is_profiling:
+ self.get_profile_and_symbols(self.profile_file)
+
if not self.dm.removeDir(self.remote_profile_dir):
print "WARNING: Failed to remove profile (%s) from device" % self.remote_profile_dir

0 comments on commit 22b5587

Please sign in to comment.