Bug 384341 - Talos Mac support. Also add environment variable settings,

quit.js to quit the browser afters tests, and ffprofile_unix with mac and
linux code. r=alice
This commit is contained in:
zach@zachlipton.com 2007-07-23 12:21:37 -07:00
Родитель da953c617e
Коммит cf775fc4fe
19 изменённых файлов: 703 добавлений и 107 удалений

Просмотреть файл

@ -9,19 +9,28 @@ these performance tests:
Make sure to correctly set the path to python in the paths.py file.
After you download and install Python 2.4, you'll need to install
After you download and install Python 2.4, Windows users will need to install
some extensions:
* Python Win32 Extensions
These extensions provide some support for process management and
performance monitoring.
http://prdownloads.sourceforge.net/pywin32/pywin32-208.win32-py2.4.exe?download
Mac users may use Python 2.3.5, included with Mac OS X, but they will need
to download the 'subprocess.py' file included with more recent Python distributions
and install it in the library path (/Library/Python/2.3/site-packages/).
* Apache HTTP Server
Found at http://httpd.apache.org/
The page cycler works on a local Apache server. After installing Apache simply place
the page_load_test/ directory into htdocs/ directory of Apache (found on most systems
at c:\Program Files\Apache Software Foundation\Apache2.2\htdocs)
* Syck YAML Parser
You'll need to download and install Syck from http://whytheluckystiff.net/syck/
Install the binary (a standard install) and the Python extension:
cd ext/python/ && python setup.py build && sudo python setup.py install
1. Make sure the prerequisites, above, are installed.
2. Copy this entire directory and all subdirectories onto your local disk
@ -32,41 +41,8 @@ these performance tests:
network.proxy.http_port:80 - these settings ensure that the browser will only
contact local web pages and will not attempt to pull information from the live web,
this is important for collecting consistant testing results.
It should look something like this:
# Filename will be appended to the timestamp in the report filename.
# Use letters and underscores only
filename: testfilename
# The title of the report
title: testtitle
# Name of profile to test
Test profile 1:
# Path to Firefox to test
firefox: C:\cygwin\tmp\test\firefox\firefox\firefox.exe
branch: testbranch
branchid: testbranchid
profile_path: C:\win32\base_profile
# Preferences to set in the test (use "preferences : {}" for no prefs)
preferences :
browser.shell.checkDefaultBrowser : false
dom.allow_scripts_to_close_windows : true
dom.disable_open_during_load: false
browser.dom.window.dump.enabled: true
network.proxy.type : 1
network.proxy.http : localhost
network.proxy.http_port : 80
# Extensions to install in test (use "extensions: {}" for none)
extensions :
# Need quotes around guid because of curly braces
"{12345678-1234-1234-1234-abcd12345678}" : c:\path\to\unzipped\xpi
foo@sample.com : c:\path\to\other\unzipped\xpi
Your config file should look something like the sample.config file in the
Talos distribution.
5. Provide a pages/ directory
The page_load_test/ relies upon having a pages directory that includes the web pages

Просмотреть файл

@ -0,0 +1,8 @@
// This file is needed to work around a Firefox bug where capability.principal
// prefs in user.js don't get recognized until the second browser launch
// which is too late for our purposes of using quit.js. Loading the principals
// from prefs.js avoids this issue.
user_pref("capability.principal.codebase.p0.granted", "UniversalPreferencesWrite UniversalXPConnect UniversalPreferencesRead");
user_pref("capability.principal.codebase.p0.id", "file://");
user_pref("capability.principal.codebase.p0.subjectName", "");
user_pref("signed.applets.codebase_principal_support", true);

Просмотреть файл

@ -41,7 +41,6 @@
__author__ = 'annie.sullivan@gmail.com (Annie Sullivan)'
BROWSER_HEIGHT = 768
BROWSER_WIDTH = 1024
@ -85,12 +84,14 @@ TP_RESOLUTION = 1
"""Run page load test.
For possible values of counters argument on Windows, see
http://technet2.microsoft.com/WindowsServer/en/Library/86b5d116-6fb3-427b-af8c-9077162125fe1033.mspx?mfr=true
Possible values on Linux:
Possible values on Linux and Mac:
'Private Bytes', '% Processor Time', 'RSS'
"""
COUNTERS = ['Private Bytes', 'Working Set', '% Processor Time']
"""URL for the results server"""
RESULTS_SERVER = 'graphserver.url.here'
RESULTS_LINK = '/bulk.cgi'
"""Enable/disable debugging output"""
DEBUG = 0

Просмотреть файл

@ -45,6 +45,7 @@ import platform
import os
import re
import time
import subprocess
import config
@ -52,6 +53,8 @@ if platform.system() == "Linux":
from ffprocess_linux import *
elif platform.system() == "Windows":
from ffprocess_win32 import *
elif platform.system() == "Darwin":
from ffprocess_mac import *
@ -61,7 +64,7 @@ def SyncAndSleep():
"""
os.spawnl(os.P_WAIT, config.SYNC)
time.sleep(3)
time.sleep(5)
def RunProcessAndWaitForOutput(command, process_name, output_regex, timeout):
@ -83,7 +86,8 @@ def RunProcessAndWaitForOutput(command, process_name, output_regex, timeout):
"""
# Start the process
handle = os.popen(command)
process = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True, shell=True, env=os.environ)
handle = process.stdout
# Wait for it to print output, terminate, or time out.
time_elapsed = 0
@ -96,7 +100,7 @@ def RunProcessAndWaitForOutput(command, process_name, output_regex, timeout):
(bytes, current_output) = NonBlockingReadProcessOutput(handle)
output += current_output
result = output_regex.search(output)
if result:
try:

Просмотреть файл

@ -0,0 +1,164 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is standalone Firefox Mac performance test.
#
# The Initial Developer of the Original Code is Google Inc.
# Portions created by the Initial Developer are Copyright (C) 2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Annie Sullivan <annie.sullivan@gmail.com> (original author)
# Ben Hearsum <bhearsum@wittydomain.com> (OS independence)
# Zach Lipton <zach@zachlipton.com> (Mac port)
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import subprocess
import signal
import os
import time
import config
from select import select
def GenerateFirefoxCommandLine(firefox_path, profile_dir, url):
"""Generates the command line for a process to run Firefox
Args:
firefox_path: String containing the path to the firefox binary to use
profile_dir: String containing the directory of the profile to run Firefox in
url: String containing url to start with.
"""
profile_arg = ''
if profile_dir:
profile_arg = '-profile %s' % profile_dir
url_arg = ''
if url:
url_arg = '-url %s' % url
cmd = '%s %s %s -width %d -height %d' % (firefox_path,
profile_arg,
url_arg,
config.BROWSER_WIDTH,
config.BROWSER_HEIGHT)
return cmd
def GetPidsByName(process_name):
"""Searches for processes containing a given string.
Args:
process_name: The string to be searched for
Returns:
A list of PIDs containing the string. An empty list is returned if none are
found.
"""
matchingPids = []
command = ['ps -Axc']
handle = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True, shell=True)
# wait for the process to terminate
handle.wait()
data = handle.stdout.readlines()
# find all matching processes and add them to the list
for line in data:
if line.find(process_name) >= 0:
# splits by whitespace, the first one should be the pid
pid = int(line.split()[0])
matchingPids.append(pid)
return matchingPids
def ProcessesWithNameExist(process_name):
"""Returns true if there are any processes running with the
given name. Useful to check whether a Firefox process is still running
Args:
process_name: String containing the process name, i.e. "firefox"
Returns:
True if any processes with that name are running, False otherwise.
"""
pids = GetPidsByName(process_name)
return len(pids) > 0
def TerminateProcess(pid):
"""Helper function to terminate a process, given the pid
Args:
pid: integer process id of the process to terminate.
"""
os.kill(pid, signal.SIGTERM)
def TerminateAllProcesses(process_name):
"""Helper function to terminate all processes with the given process name
Args:
process_name: String containing the process name, i.e. "firefox"
"""
pids = GetPidsByName(process_name)
for pid in pids:
TerminateProcess(pid)
def NonBlockingReadProcessOutput(handle):
"""Does a non-blocking read from the output of the process
with the given handle.
Args:
handle: The process handle returned from os.popen()
Returns:
A tuple (bytes, output) containing the number of output
bytes read, and the actual output.
"""
output = ""
num_avail = 0
# check for data
# select() does not seem to work well with pipes.
# after data is available once it *always* thinks there is data available
# readline() will continue to return an empty string however
# so we can use this behavior to work around the problem
while select([handle], [], [], 0)[0]:
line = handle.readline()
if line:
output += line
else:
break
# this statement is true for encodings that have 1byte/char
num_avail = len(output)
return (num_avail, output)

Просмотреть файл

@ -53,14 +53,16 @@ import shutil
import tempfile
import time
import utils
import ffprocess
import config
if platform.system() == "Linux":
from ffprofile_linux import *
from ffprofile_unix import *
elif platform.system() == "Windows":
from ffprofile_win32 import *
elif platform.system() == "Darwin":
from ffprofile_unix import *
def PrefString(name, value, newline):
"""Helper function to create a pref string for Firefox profile prefs.js
@ -150,5 +152,6 @@ def InitializeNewProfile(firefox_path, profile_dir):
time.sleep(5)
if not ffprocess.ProcessesWithNameExist("firefox"):
return
utils.debug("terminating firefox process")
ffprocess.TerminateAllProcesses("firefox")
ffprocess.SyncAndSleep()

Просмотреть файл

@ -20,6 +20,7 @@
# Contributor(s):
# Annie Sullivan <annie.sullivan@gmail.com> (original author)
# Ben Hearsum <bhearsum@wittydomain.com> (OS independence)
# Zach Lipton <zach@zachlipton.com> (Mac port)
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or

Просмотреть файл

@ -39,13 +39,15 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<script language="Javascript" type="text/javascript" src="page_load_test/quit.js"></script>
<title>shutdown script</title>
</head>
<body onload="
if (window.dump) {
dump('\n\n__metricsScreen width:' + screen.width + ' Screen height:' + screen.height + ' colorDepth:' + screen.colorDepth);
dump('__metricsScreen width:' + screen.width + ' Screen height:' + screen.height + ' colorDepth:' + screen.colorDepth + '\n\n');
}
goQuitApplication();
window.close();
">
</body>

Просмотреть файл

@ -38,10 +38,11 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<script language="Javascript" type="text/javascript" src="page_load_test/quit.js"></script>
<title>shutdown script</title>
</head>
<body onload="window.close();">
<body onload="goQuitApplication(); window.close();">
This page should close Firefox. If it does not, please make sure that
the dom.allow_scripts_to_close_windows preference is set to true in
<a href="about:config">about:config</a>

Просмотреть файл

@ -1,10 +1,12 @@
<html>
<script language="Javascript" type="text/javascript" src="parray.js"></script>
<script language="Javascript" type="text/javascript" src="quit.js"></script>
<script>
var NUM_PAGES;
var NUM_CYCLES;
var DEFAULT_TIMEOUT = 25000; //how long any given page can take to load
var QUIT; // whether to quit the app (with quit.js) after the tests
var t;
function parseParams() {
@ -24,6 +26,9 @@
pages = pages.concat(pages_i18n);
}
break;
case 'quit':
QUIT = (fields[1] - 0);
break;
}
}
if (!NUM_PAGES)
@ -155,7 +160,7 @@
}
dump("\n")
}
dump("__end_tp_report\n");
dump("__end_tp_report\n\n");
//rstring += "__end_page_load_report";
//alert(rstring);
//dump(rstring);
@ -203,6 +208,13 @@
if (cycle == NUM_CYCLES) {
showReport();
dumpReport();
// use quit.js to quit the app if the quit param is true and
// we have universalxpconnect privs:
if (QUIT && canQuitApplication()) {
goQuitApplication();
}
window.close();
return;
}

Просмотреть файл

@ -0,0 +1,143 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla Automated Testing Code
*
* The Initial Developer of the Original Code is
* Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2005
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): Bob Clary <bob@bclary.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
From mozilla/toolkit/content
These files did not have a license
*/
function canQuitApplication()
{
var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
if (!os)
{
return true;
}
try
{
var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
.createInstance(Components.interfaces.nsISupportsPRBool);
os.notifyObservers(cancelQuit, "quit-application-requested", null);
// Something aborted the quit process.
if (cancelQuit.data)
{
return false;
}
}
catch (ex)
{
}
os.notifyObservers(null, "quit-application-granted", null);
return true;
}
function goQuitApplication()
{
const privs = 'UniversalPreferencesRead UniversalPreferencesWrite ' +
'UniversalXPConnect';
try
{
netscape.security.PrivilegeManager.enablePrivilege(privs);
}
catch(ex)
{
throw('goQuitApplication: privilege failure ' + ex);
}
if (!canQuitApplication())
{
return false;
}
const kAppStartup = '@mozilla.org/toolkit/app-startup;1';
const kAppShell = '@mozilla.org/appshell/appShellService;1';
var appService;
var forceQuit;
if (kAppStartup in Components.classes)
{
appService = Components.classes[kAppStartup].
getService(Components.interfaces.nsIAppStartup);
forceQuit = Components.interfaces.nsIAppStartup.eForceQuit;
}
else if (kAppShell in Components.classes)
{
appService = Components.classes[kAppShell].
getService(Components.interfaces.nsIAppShellService);
forceQuit = Components.interfaces.nsIAppShellService.eForceQuit;
}
else
{
throw 'goQuitApplication: no AppStartup/appShell';
}
var windowManager = Components.
classes['@mozilla.org/appshell/window-mediator;1'].getService();
var windowManagerInterface = windowManager.
QueryInterface(Components.interfaces.nsIWindowMediator);
var enumerator = windowManagerInterface.getEnumerator(null);
while (enumerator.hasMoreElements())
{
var domWindow = enumerator.getNext();
if (("tryToClose" in domWindow) && !domWindow.tryToClose())
{
return false;
}
domWindow.close();
}
try
{
appService.quit(forceQuit);
}
catch(ex)
{
throw('goQuitApplication: ' + ex);
}
return true;
}

Просмотреть файл

@ -58,6 +58,7 @@ import string
import socket
socket.setdefaulttimeout(480)
import utils
import config
import post_file
import tp
@ -80,7 +81,7 @@ def process_Request(post):
lines = post.split('\n')
for line in lines:
if line.find("RETURN:") > -1:
str += line.rsplit(":")[3] + ":" + shortNames(line.rsplit(":")[1]) + ":" + line.rsplit(":")[2] + '\n'
str += line.split(":")[3] + ":" + shortNames(line.split(":")[1]) + ":" + line.split(":")[2] + '\n'
return str
def test_file(filename):
@ -117,7 +118,8 @@ def test_file(filename):
yaml[item]['firefox'],
yaml[item]['branch'],
yaml[item]['branchid'],
yaml[item]['profile_path']]
yaml[item]['profile_path'],
yaml[item]['env']]
test_configs.append(new_config)
test_names.append(item)
config_file.close()
@ -230,11 +232,11 @@ def test_file(filename):
url = url_format % (config.RESULTS_SERVER, values[0],)
link = link_format % (url, linkName,)
print "RETURN: " + link
if __name__=='__main__':
# Read in each config file and run the tests on it.
for i in range(1, len(sys.argv)):
utils.debug("running test file " + sys.argv[i])
test_file(sys.argv[i])

Просмотреть файл

@ -1,3 +1,5 @@
# Sample Talos configuration file
# Filename will be appended to the timestamp in the report filename.
# Use letters and underscores only
filename: testfilename
@ -28,8 +30,19 @@ Test profile 1:
dom.disable_window_flip : true
dom.disable_window_move_resize : true
security.enable_java : false
extensions.checkCompatibility : false
extensions.update.notifyUser: false
capability.principal.codebase.p0.granted : UniversalPreferencesWrite UniversalXPConnect UniversalPreferencesRead
capability.principal.codebase.p0.id : file://
capability.principal.codebase.p1.granted : UniversalXPConnect
# Extensions to install in test (use "extensions: {}" for none)
#extensions:
# Need quotes around guid because of curly braces
# extensions :
# "{12345678-1234-1234-1234-abcd12345678}" : c:\path\to\unzipped\xpi
# foo@sample.com : c:\path\to\other\unzipped\xpi
extensions : {}
# Environment variables to set during test (use env: {} for none)
env :
NO_EM_RESTART : 1

Просмотреть файл

@ -33,17 +33,23 @@
- and other provisions required by the LGPL or the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** --> <!-- Pick off begin time as a cgi argument and print it out --> <html> <!-- call this with an arg, e.g. file://foo/startup-test.html?begin=12345678 --> <!-- In-line this to avoid compilation. -->
- ***** END LICENSE BLOCK ***** -->
<!-- Pick off begin time as a cgi argument and print it out -->
<html>
<!-- call this with an arg, e.g. file://foo/startup-test.html?begin=12345678 -->
<!-- In-line this to avoid compilation. -->
<body onload="
var now = (new Date()).getTime();
var begin = document.location.search.split('=')[1]; // ?begin=nnnnn
var startupTime = now - begin;
document.write('\n\nStartup time = ' + startupTime + ' ms<br>');
if (window.dump) {
dump('\n\n__startuptime,' + startupTime + '\n\n');
dump('__startuptime,' + startupTime + '\n\n');
}
window.close();
">
">
</body>
</html>
</html>

Просмотреть файл

@ -55,6 +55,8 @@ import re
import shutil
import time
import sys
import subprocess
import utils
import ffprocess
import ffprofile
@ -65,6 +67,8 @@ if platform.system() == "Linux":
from tp_linux import *
elif platform.system() == "Windows":
from tp_win32 import *
elif platform.system() == "Darwin":
from tp_mac import *
# Regular expression to get stats for page load test (Tp)
@ -105,10 +109,11 @@ def RunPltTests(profile_configs,
plt_results = []
results_string = []
for pconfig in profile_configs:
print "in tp"
utils.debug("running pageload tests")
print pconfig
sys.stdout.flush()
rstring = ""
utils.setEnvironmentVars(pconfig[6])
# Create the new profile
profile_dir = ffprofile.CreateTempProfileDir(pconfig[5],
pconfig[0],
@ -127,9 +132,10 @@ def RunPltTests(profile_configs,
timeout = 10000
total_time = 0
output = ''
url = config.TP_URL + '?cycles=' + str(num_cycles)
url = config.TP_URL + '?quit=1&cycles=' + str(num_cycles)
command_line = ffprocess.GenerateFirefoxCommandLine(pconfig[2], profile_dir, url)
handle = os.popen(command_line)
process = subprocess.Popen(command_line, stdout=subprocess.PIPE, universal_newlines=True, shell=True, bufsize=0, env=os.environ)
handle = process.stdout
# give firefox a chance to open
time.sleep(1)
@ -185,6 +191,8 @@ def RunPltTests(profile_configs,
ffprofile.MakeDirectoryContentsWritable(profile_dir)
shutil.rmtree(profile_dir)
utils.restoreEnvironmentVars()
counter_data.append(counts)
results_string.append(rstring)

Просмотреть файл

@ -95,49 +95,8 @@ def GetResidentSize(pid):
def GetCpuTime(pid, sampleTime=1):
"""Calculates the percent of time a process spent executing
This function samples /proc/PID/status at a rate of 20samples/sec to
check whether a process is active or idle.
Args:
pid: The PID of process to calculate time for
sampleTime: The length of time to monitor the process for. If this
argument is unspecified or 0 it will be monitored until
it terminates
Returns:
A percent of time a PID was idle (between 0.00 and 100.00)
"""
file = '/proc/%s/status' % pid
sampleInterval = .05
totalSamples = (1 / sampleInterval) * sampleTime
states = []
while os.path.exists(file):
status = open(file)
for line in status:
if line.find("State") >= 0:
states.append(line.split()[1])
# check to see if we've reached the maximum number of samples
if totalSamples != 0 and len(states) >= totalSamples:
break;
time.sleep(sampleInterval);
# non-idle cpu time = active cpu time / total cpu time
# ex: 10 total cycles, 8 idle cycles, 2 active cycles
# non-idle cpu time = 2 / 10
# non-idle cpu time = .2 (20%)
try:
activeCpuTime = float(states.count("R")) / float(len(states))
except ZeroDivisionError:
activeCpuTime = -1
return float("%.2lf" % (activeCpuTime * 100))
# return all zeros on this platform as per the 7/18/07 perf meeting
return 0
counterDict = {}
counterDict["Private Bytes"] = GetPrivateBytes

Просмотреть файл

@ -0,0 +1,212 @@
#!/usr/bin/env python
#
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is standalone Firefox Windows performance test.
#
# The Initial Developer of the Original Code is Google Inc.
# Portions created by the Initial Developer are Copyright (C) 2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Annie Sullivan <annie.sullivan@gmail.com> (original author)
# Ben Hearsum <bhearsum@wittydomain.com> (ported to linux)
# Zach Lipton <zach@zachlipton.com> (Mac port)
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
"""A set of functions to run the Tp test.
The Tp test measures page load times in Firefox. It does this with a
JavaScript script that opens a new window and cycles through page loads
from the local disk, timing each one. The script dumps the sum of the
mean times to open each page, and the standard deviation, to standard out.
We can also measure performance attributes during the test. See below for
what can be monitored
"""
__author__ = 'annie.sullivan@gmail.com (Annie Sullivan)'
import os
import time
import threading
import subprocess
import ffprocess
def GetProcessData(pid):
"""Runs a ps on the process identified by pid and returns the output line
as a list (uid, pid, ppid, cpu, pri, ni, vsz, rss, wchan, stat, tt, time, command)
"""
command = ['ps -Acup'+str(pid)]
handle = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True, shell=True)
handle.wait()
data = handle.stdout.readlines()
# find all matching processes and add them to the list
for line in data:
if line.find(str(pid)) >= 0:
# splits by whitespace
line = line.split()
if (line[1] == str(pid)):
return line
def GetPrivateBytes(pid):
"""Calculate the amount of private, writeable memory allocated to a process.
"""
psData = GetProcessData(pid)
return psData[5]
def GetResidentSize(pid):
"""Retrieve the current resident memory for a given process"""
psData = GetProcessData(pid)
return psData[4]
def GetCpuTime(pid):
# return all zeros for now on this platform as per 7/18/07 perf meeting
return 0
counterDict = {}
counterDict["Private Bytes"] = GetPrivateBytes
counterDict["RSS"] = GetResidentSize
counterDict["% Processor Time"] = GetCpuTime
class CounterManager(threading.Thread):
"""This class manages the monitoring of a process with any number of
counters.
A counter can be any function that takes an argument of one pid and
returns a piece of data about that process.
Some examples are: CalcCPUTime, GetResidentSize, and GetPrivateBytes
"""
pollInterval = .25
def __init__(self, process, counters=None):
"""Args:
counters: A list of counters to monitor. Any counters whose name does
not match a key in 'counterDict' will be ignored.
"""
self.allCounters = {}
self.registeredCounters = {}
self.process = process
self.runThread = False
self.pid = -1
self._loadCounters()
self.registerCounters(counters)
threading.Thread.__init__(self)
def _loadCounters(self):
"""Loads all of the counters defined in the counterDict"""
for counter in counterDict.keys():
self.allCounters[counter] = counterDict[counter]
def registerCounters(self, counters):
"""Registers a list of counters that will be monitoring.
Only counters whose names are found in allCounters will be added
"""
for counter in counters:
if counter in self.allCounters:
self.registeredCounters[counter] = \
[self.allCounters[counter], []]
def unregisterCounters(self, counters):
"""Unregister a list of counters.
Only counters whose names are found in registeredCounters will be
paid attention to
"""
for counter in counters:
if counter in self.registeredCounters:
del self.registeredCounters[counter]
def getRegisteredCounters(self):
"""Returns a list of the registered counters."""
return keys(self.registeredCounters)
def getCounterValue(self, counterName):
"""Returns the last value of the counter 'counterName'"""
try:
if counterName is "% Processor Time":
return self._getCounterAverage(counterName)
else:
return self.registeredCounters[counterName][1][-1]
except:
return None
def _getCounterAverage(self, counterName):
"""Returns the average value of the counter 'counterName'"""
try:
total = 0
for v in self.registeredCounters[counterName][1]:
total += v
return total / len(self.registeredCounters[counterName][1])
except:
return None
def getProcess(self):
"""Returns the process currently associated with this CounterManager"""
return self.process
def startMonitor(self):
"""Starts the monitoring process.
Throws an exception if any error occurs
"""
# TODO: make this function less ugly
try:
# the last process is the useful one
self.pid = ffprocess.GetPidsByName(self.process)[-1]
self.runThread = True
self.start()
except:
raise
def stopMonitor(self):
"""Stops the monitor"""
# TODO: should probably wait until we know run() is completely stopped
# before setting self.pid to None. Use a lock?
self.runThread = False
def run(self):
"""Performs the actual monitoring of the process. Will keep running
until stopMonitor() is called
"""
while self.runThread:
for counter in self.registeredCounters.keys():
# counter[0] is a function that gets the current value for
# a counter
# counter[1] is a list of recorded values
try:
self.registeredCounters[counter][1].append(
self.registeredCounters[counter][0](self.pid))
except:
# if a counter throws an exception, remove it
self.unregisterCounters([counter])
time.sleep(self.pollInterval)

Просмотреть файл

@ -52,6 +52,7 @@ import re
import shutil
import time
import utils
import ffprocess
import ffprofile
import ffinfo
@ -91,6 +92,7 @@ def RunStartupTest(firefox_path, profile_dir, num_runs, timeout):
startup_times = []
for i in range(-1, num_runs):
# Make sure we don't get "Firefox is already running..." errors
utils.debug("syncing and sleeping")
ffprocess.SyncAndSleep()
# Create a command line that passes in the url of the startup test
@ -98,15 +100,19 @@ def RunStartupTest(firefox_path, profile_dir, num_runs, timeout):
time_arg = int(time.time() * 1000)
url = config.TS_URL + str(time_arg)
command_line = ffprocess.GenerateFirefoxCommandLine(firefox_path, profile_dir, url)
utils.debug("about to run ts test iteration")
(match, timed_out) = ffprocess.RunProcessAndWaitForOutput(command_line,
'firefox',
TS_REGEX,
timeout)
if match > 0:
utils.debug("ts output match is " + match)
else:
utils.debug("ts failed to match")
if timed_out or not IsInteger(match):
match = None
if i > -1 and match and match > 0:
startup_times.append(match)
return startup_times
@ -123,18 +129,22 @@ def RunStartupTests(profile_configs, num_runs):
Returns:
Array of arrays of startup times, one for each profile.
"""
utils.debug("Running startup time tests")
all_times = []
for config in profile_configs:
utils.setEnvironmentVars(config[6])
# Create the new profile
profile_dir = ffprofile.CreateTempProfileDir(config[5],
config[0],
config[1])
utils.debug("temp profile dir created")
# Run Firefox once with new profile so initializing it doesn't
# cause a performance hit, and the second Firefox that gets
# created is properly terminated.
utils.debug("initializing new profile")
ffprofile.InitializeNewProfile(config[2], profile_dir)
utils.debug("getting configuration metrics from browser")
ffinfo.GetMetricsFromBrowser(config[2], profile_dir)
# Run the startup tests for this profile and log the results.
@ -147,5 +157,7 @@ def RunStartupTests(profile_configs, num_runs):
ffprocess.SyncAndSleep()
ffprofile.MakeDirectoryContentsWritable(profile_dir)
shutil.rmtree(profile_dir)
utils.restoreEnvironmentVars()
return all_times

Просмотреть файл

@ -0,0 +1,69 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is standalone Firefox performance tests.
#
# The Initial Developer of the Original Code is The Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Zach Lipton <zach@zachlipton.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
"""Utility functions"""
import config
import os
saved_environment = {}
def debug(message):
"""Prints a debug message to the console if the DEBUG switch is turned on
in config.py
Args:
message: string containing a debugging statement
"""
if config.DEBUG == 1:
print message
def setEnvironmentVars(newVars):
"""Sets environment variables as specified by env, an array of variables
from config.py"""
env = os.environ
for var in newVars:
# save the old values so they can be restored later:
try:
saved_environment[var] = str(env[var])
except :
saved_environment[var] = ""
env[var] = str(newVars[var])
def restoreEnvironmentVars():
"""Restores environment variables to the state they were in before
setEnvironmentVars() was last called"""
for var in saved_environment:
os.environ[var] = saved_environment[var]