From 355446c352cdcd49bb6a4d507f12c017b8968f0d Mon Sep 17 00:00:00 2001 From: Nick Hurley Date: Wed, 27 Feb 2013 13:24:20 -0800 Subject: [PATCH] Import pageloader from talos --- pageloader/README | 65 +++ pageloader/chrome.manifest | 4 + pageloader/chrome/MozillaFileLogger.js | 137 +++++ pageloader/chrome/memory.js | 95 ++++ pageloader/chrome/pageloader.js | 696 +++++++++++++++++++++++++ pageloader/chrome/pageloader.xul | 57 ++ pageloader/chrome/quit.js | 110 ++++ pageloader/chrome/report.js | 94 ++++ pageloader/components/tp-cmdline.js | 197 +++++++ pageloader/install.rdf | 20 + 10 files changed, 1475 insertions(+) create mode 100644 pageloader/README create mode 100644 pageloader/chrome.manifest create mode 100644 pageloader/chrome/MozillaFileLogger.js create mode 100644 pageloader/chrome/memory.js create mode 100644 pageloader/chrome/pageloader.js create mode 100644 pageloader/chrome/pageloader.xul create mode 100644 pageloader/chrome/quit.js create mode 100644 pageloader/chrome/report.js create mode 100644 pageloader/components/tp-cmdline.js create mode 100644 pageloader/install.rdf diff --git a/pageloader/README b/pageloader/README new file mode 100644 index 0000000..3fb905c --- /dev/null +++ b/pageloader/README @@ -0,0 +1,65 @@ +Pageload Test Component +======================= + +Usage: + + ./firefox -tp file:///path/to/manifest.txt [-tpargs...] + +See ./firefox -help for other arguments. + + +Manifest file format +==================== + +Comments in the manifest file start with a #. Each line may be: + +* a URL (absolute or relative to the manifest) + +This URL is added to the list of tests. + +* one or more flags, followed by whitespace, followed by a URL + +The only flag supported currently is '%', which indicates that +a test will do its own timing. (See Self-timing Tests below.) + +* "include" followed by whitespace, followed by a URL + +Parse the given manifest file. + +Self-timing Tests +================= + +Most timing tests are interested in timing how long it takes the page +to load; that is, from the start of page loading until the 'load' +event is dispatched. By default, this is what the pageloader will +time. However, if a test URL has the % flag, the test is expected to +report its own timing. For this purpose, the pageloader will provide +a function named "tpRecordTime" in the test's global object that it +should call once it has performed whatever timing it wants to do. +The given value will be used as the timing result for this test. + +Output format +============= + +The result is a dump to stdout via dump() -- +browser.dom.window.dump.enabled must be set to true in the profile. + +Sample output: + +__start_tp_report +_x_x_mozilla_page_load,778.5,NaN,NaN +_x_x_mozilla_page_load_details,avgmedian|778.5|average|766.75|minimum|NaN|maximum|NaN|stddev|NaN|0;file:///c:/proj/mozilla-cvs/perf/tp2/base/www.cnn.com/index.html;778.5;766.75;722;1027;1027;788;777;722;780|... +__end_tp_report + +Note that the minimum, maximum, stddev are not calculated; they're +always reported as NaN. (They were the minimum and maximum values of +any sampled value, and the standard deviation across all sampled +values -- not very useful.) + +TODO +==== + +* Command line option to choose whether to run with or without browser chrome. Currently runs without. + +* Tinderbox-dropping style output + * better yet would be to teach tinderbox about JSON diff --git a/pageloader/chrome.manifest b/pageloader/chrome.manifest new file mode 100644 index 0000000..7eaae97 --- /dev/null +++ b/pageloader/chrome.manifest @@ -0,0 +1,4 @@ +content pageloader chrome/ +component {8AF052F5-8EFE-4359-8266-E16498A82E8B} components/tp-cmdline.js +contract @mozilla.org/commandlinehandler/general-startup;1?type=tp {8AF052F5-8EFE-4359-8266-E16498A82E8B} +category command-line-handler m-tp @mozilla.org/commandlinehandler/general-startup;1?type=tp diff --git a/pageloader/chrome/MozillaFileLogger.js b/pageloader/chrome/MozillaFileLogger.js new file mode 100644 index 0000000..caf3c38 --- /dev/null +++ b/pageloader/chrome/MozillaFileLogger.js @@ -0,0 +1,137 @@ +/** + * MozillaFileLogger, a log listener that can write to a local file. + */ + +var ipcMode = false; // running in e10s build and need to use IPC? +try { + var ipcsanity = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + ipcsanity.setIntPref("mochitest.ipcmode", 0); +} catch (e) { + ipcMode = true; +} + +function contentDispatchEvent(type, data, sync) { + if (typeof(data) === "undefined") { + data = {}; + } + + var element = document.createEvent("datacontainerevent"); + element.initEvent("contentEvent", true, false); + element.setData("sync", sync); + element.setData("type", type); + element.setData("data", JSON.stringify(data)); + document.dispatchEvent(element); +} + +function contentSyncEvent(type, data) { + contentDispatchEvent(type, data, 1); +} + +function contentAsyncEvent(type, data) { + contentDispatchEvent(type, data, 0); +} + +try { + if (Cc === undefined) { + var Cc = Components.classes; + var Ci = Components.interfaces; + } +} catch (ex) {} //running in ipcMode-chrome + +try { + const FOSTREAM_CID = "@mozilla.org/network/file-output-stream;1"; + const LF_CID = "@mozilla.org/file/local;1"; + + // File status flags. It is a bitwise OR of the following bit flags. + // Only one of the first three flags below may be used. + const PR_READ_ONLY = 0x01; // Open for reading only. + const PR_WRITE_ONLY = 0x02; // Open for writing only. + const PR_READ_WRITE = 0x04; // Open for reading and writing. + + // If the file does not exist, the file is created. + // If the file exists, this flag has no effect. + const PR_CREATE_FILE = 0x08; + + // The file pointer is set to the end of the file prior to each write. + const PR_APPEND = 0x10; + + // If the file exists, its length is truncated to 0. + const PR_TRUNCATE = 0x20; + + // If set, each write will wait for both the file data + // and file status to be physically updated. + const PR_SYNC = 0x40; + + // If the file does not exist, the file is created. If the file already + // exists, no action and NULL is returned. + const PR_EXCL = 0x80; +} catch (ex) { + // probably not running in the test harness +} + +/** Init the file logger with the absolute path to the file. + It will create and append if the file already exists **/ +var MozillaFileLogger = {}; + + +MozillaFileLogger.init = function(path) { + if (ipcMode) { + contentAsyncEvent("LoggerInit", {"filename": path}); + return; + } + + MozillaFileLogger._file = Cc[LF_CID].createInstance(Ci.nsILocalFile); + MozillaFileLogger._file.initWithPath(path); + MozillaFileLogger._foStream = Cc[FOSTREAM_CID].createInstance(Ci.nsIFileOutputStream); + MozillaFileLogger._foStream.init(this._file, PR_WRITE_ONLY | PR_CREATE_FILE | PR_APPEND, + 0664, 0); +} + +MozillaFileLogger.getLogCallback = function() { + if (ipcMode) { + return function(msg) { + contentAsyncEvent("Logger", {"num": msg.num, "level": msg.level, "info": msg.info.join(' ')}); + } + } + + return function (msg) { + var data = msg.num + " " + msg.level + " " + msg.info.join(' ') + "\n"; + if (MozillaFileLogger._foStream) + MozillaFileLogger._foStream.write(data, data.length); + + if (data.indexOf("SimpleTest FINISH") >= 0) { + MozillaFileLogger.close(); + } + } +} + +// This is only used from chrome space by the reftest harness +MozillaFileLogger.log = function(msg) { + if (MozillaFileLogger._foStream) + MozillaFileLogger._foStream.write(msg, msg.length); +} + +MozillaFileLogger.close = function() { + if (ipcMode) { + contentAsyncEvent("LoggerClose"); + return; + } + + if(MozillaFileLogger._foStream) + MozillaFileLogger._foStream.close(); + + MozillaFileLogger._foStream = null; + MozillaFileLogger._file = null; +} + +if (ipcMode == false) { + try { + var prefs = Components.classes['@mozilla.org/preferences-service;1'] + .getService(Components.interfaces.nsIPrefBranch2); + var filename = prefs.getCharPref('talos.logfile'); + MozillaFileLogger.init(filename); + } catch (ex) {} //pref does not exist, return empty string +} + + diff --git a/pageloader/chrome/memory.js b/pageloader/chrome/memory.js new file mode 100644 index 0000000..c3bdaed --- /dev/null +++ b/pageloader/chrome/memory.js @@ -0,0 +1,95 @@ + +var gChildProcess = true; +var gMemCallback = null; + + +/* + * Initialize memory collector. Determine if we have a child process. + */ +function initializeMemoryCollector(callback, args) { + gMemCallback = function() { return callback(args); }; + + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + + os.addObserver(function () { + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + + memTimer.cancel(); + memTimer = null; + + os.removeObserver(arguments.callee, "child-memory-reporter-update", false); + os.addObserver(collectAndReport, "child-memory-reporter-update", false); + gMemCallback(); + }, "child-memory-reporter-update", false); + + /* + * Assume we have a child process, but if timer fires before we call the observer + * we will assume there is no child process. + */ + var event = { + notify: function(timer) { + memTimer = null; + gChildProcess = false; + gMemCallback(); + } + } + + memTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + memTimer.initWithCallback(event, 10000, Components.interfaces.nsITimer.TYPE_ONE_SHOT); + + os.notifyObservers(null, "child-memory-reporter-request", null); +} + +/* + * Collect memory from all processes and callback when done collecting. + */ +function collectMemory(callback, args) { + gMemCallback = function() { return callback(args); }; + + if (gChildProcess) { + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + + os.notifyObservers(null, "child-memory-reporter-request", null); + } else { + collectAndReport(null, null, null); + } +} + +function collectAndReport(aSubject, aTopic, aData) { + dumpLine(collectRSS()); + gMemCallback(); +} + +function collectRSS() { + var mgr = Components.classes["@mozilla.org/memory-reporter-manager;1"]. + getService(Components.interfaces.nsIMemoryReporterManager); + var e = mgr.enumerateReporters(); + text = ""; + while (e.hasMoreElements()) { + var reporter = e.getNext().QueryInterface(Components.interfaces.nsIMemoryReporter); + if (reporter.path == 'resident') { + procName = reporter.process; + if (procName == '') + procName = "Main"; + + //For content process it is in the format "Content ()", we just want Content + procName = procName.split(' ')[0]; + text += "RSS: " + procName + ": " + reporter.amount + "\n"; + } + } + return text; +} + +/* + * Cleanup and stop memory collector. + */ +function stopMemCollector() { + if (gChildProcess) { + var os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + os.removeObserver(collectAndReport, "child-memory-reporter-update"); + } +} diff --git a/pageloader/chrome/pageloader.js b/pageloader/chrome/pageloader.js new file mode 100644 index 0000000..cb0042c --- /dev/null +++ b/pageloader/chrome/pageloader.js @@ -0,0 +1,696 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +try { + if (Cc === undefined) { + var Cc = Components.classes; + var Ci = Components.interfaces; + } +} catch (ex) {} + +var NUM_CYCLES = 5; +var numPageCycles = 1; + +var pageFilterRegexp = null; +var useBrowser = true; +var winWidth = 1024; +var winHeight = 768; + +var doRenderTest = false; + +var pages; +var pageIndex; +var start_time; +var cycle; +var pageCycle; +var report; +var noisy = false; +var timeout = -1; +var delay = 250; +var timeoutEvent = -1; +var running = false; +var forceCC = true; +var reportRSS = true; + +var useMozAfterPaint = false; +var gPaintWindow = window; +var gPaintListener = false; + +//when TEST_DOES_OWN_TIMING, we need to store the time from the page as MozAfterPaint can be slower than pageload +var gTime = -1; +var gStartTime = -1; +var gReference = -1; + +var content; + +var TEST_DOES_OWN_TIMING = 1; + +var browserWindow = null; + +var recordedName = null; +var pageUrls; + +// the io service +var gIOS = null; + +function plInit() { + if (running) { + return; + } + running = true; + + cycle = 0; + pageCycle = 1; + + try { + var args = window.arguments[0].wrappedJSObject; + + var manifestURI = args.manifest; + var startIndex = 0; + var endIndex = -1; + if (args.startIndex) startIndex = parseInt(args.startIndex); + if (args.endIndex) endIndex = parseInt(args.endIndex); + if (args.numCycles) NUM_CYCLES = parseInt(args.numCycles); + if (args.numPageCycles) numPageCycles = parseInt(args.numPageCycles); + if (args.width) winWidth = parseInt(args.width); + if (args.height) winHeight = parseInt(args.height); + if (args.filter) pageFilterRegexp = new RegExp(args.filter); + if (args.noisy) noisy = true; + if (args.timeout) timeout = parseInt(args.timeout); + if (args.delay) delay = parseInt(args.delay); + if (args.mozafterpaint) useMozAfterPaint = true; + if (args.rss) reportRSS = true; + + forceCC = !args.noForceCC; + doRenderTest = args.doRender; + + if (forceCC && + !window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils) + .garbageCollect) { + forceCC = false; + } + + gIOS = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + if (args.offline) + gIOS.offline = true; + var fileURI = gIOS.newURI(manifestURI, null, null); + pages = plLoadURLsFromURI(fileURI); + + if (!pages) { + dumpLine('tp: could not load URLs, quitting'); + plStop(true); + } + + if (pages.length == 0) { + dumpLine('tp: no pages to test, quitting'); + plStop(true); + } + + if (startIndex < 0) + startIndex = 0; + if (endIndex == -1 || endIndex >= pages.length) + endIndex = pages.length-1; + if (startIndex > endIndex) { + dumpLine("tp: error: startIndex >= endIndex"); + plStop(true); + } + + pages = pages.slice(startIndex,endIndex+1); + pageUrls = pages.map(function(p) { return p.url.spec.toString(); }); + report = new Report(); + + if (doRenderTest) + renderReport = new Report(); + + pageIndex = 0; + + if (args.useBrowserChrome) { + var wwatch = Cc["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Ci.nsIWindowWatcher); + var blank = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + blank.data = "about:blank"; + browserWindow = wwatch.openWindow + (null, "chrome://browser/content/", "_blank", + "chrome,all,dialog=no,width=" + winWidth + ",height=" + winHeight, blank); + + gPaintWindow = browserWindow; + // get our window out of the way + window.resizeTo(10,10); + + var browserLoadFunc = function (ev) { + browserWindow.removeEventListener('load', browserLoadFunc, true); + + // do this half a second after load, because we need to be + // able to resize the window and not have it get clobbered + // by the persisted values + setTimeout(function () { + browserWindow.resizeTo(winWidth, winHeight); + browserWindow.moveTo(0, 0); + browserWindow.focus(); + + content = browserWindow.getBrowser(); + + // Load the frame script for e10s / IPC message support + if (content.getAttribute("remote") == "true") { + let contentScript = "data:,function _contentLoadHandler(e) { " + + " if (e.originalTarget.defaultView == content) { " + + " content.wrappedJSObject.tpRecordTime = function(t, s) { sendAsyncMessage('PageLoader:RecordTime', { time: t, startTime: s }); }; "; + if (useMozAfterPaint) { + contentScript += "" + + "function _contentPaintHandler() { " + + " var utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils); " + + " if (utils.isMozAfterPaintPending) { " + + " addEventListener('MozAfterPaint', function(e) { " + + " removeEventListener('MozAfterPaint', arguments.callee, true); " + + " sendAsyncMessage('PageLoader:MozAfterPaint', {}); " + + " }, true); " + + " } else { " + + " sendAsyncMessage('PageLoader:MozAfterPaint', {}); " + + " } " + + "}; " + + "content.wrappedJSObject.setTimeout(_contentPaintHandler, 0); "; + } else { + contentScript += " sendAsyncMessage('PageLoader:Load', {}); "; + } + contentScript += "" + + " }" + + "} " + + "addEventListener('load', _contentLoadHandler, true); "; + content.messageManager.loadFrameScript(contentScript, false); + } + if (reportRSS) { + initializeMemoryCollector(plLoadPage, 100); + } else { + setTimeout(plLoadPage, 100); + } + }, 500); + }; + + browserWindow.addEventListener('load', browserLoadFunc, true); + } else { + gPaintWindow = window; + window.resizeTo(winWidth, winHeight); + + content = document.getElementById('contentPageloader'); + + if (reportRSS) { + initializeMemoryCollector(plLoadPage, delay); + } else { + setTimeout(plLoadPage, delay); + } + } + } catch(e) { + dumpLine(e); + plStop(true); + } +} + +function plPageFlags() { + return pages[pageIndex].flags; +} + +// load the current page, start timing +var removeLastAddedListener = null; +var removeLastAddedMsgListener = null; +function plLoadPage() { + var pageName = pages[pageIndex].url.spec; + + if (removeLastAddedListener) + removeLastAddedListener(); + + if (removeLastAddedMsgListener) + removeLastAddedMsgListener(); + + if (plPageFlags() & TEST_DOES_OWN_TIMING) { + // if the page does its own timing, use a capturing handler + // to make sure that we can set up the function for content to call + + content.addEventListener('load', plLoadHandlerCapturing, true); + removeLastAddedListener = function() { + content.removeEventListener('load', plLoadHandlerCapturing, true); + if (useMozAfterPaint) { + content.removeEventListener("MozAfterPaint", plPaintedCapturing, true); + gPaintListener = false; + } + }; + } else { + // if the page doesn't do its own timing, use a bubbling handler + // to make sure that we're called after the page's own onload() handling + + // XXX we use a capturing event here too -- load events don't bubble up + // to the element. See bug 390263. + content.addEventListener('load', plLoadHandler, true); + removeLastAddedListener = function() { + content.removeEventListener('load', plLoadHandler, true); + if (useMozAfterPaint) { + gPaintWindow.removeEventListener("MozAfterPaint", plPainted, true); + gPaintListener = false; + } + }; + } + + // If the test browser is remote (e10s / IPC) we need to use messages to watch for page load + if (content.getAttribute("remote") == "true") { + content.messageManager.addMessageListener('PageLoader:Load', plLoadHandlerMessage); + content.messageManager.addMessageListener('PageLoader:RecordTime', plRecordTimeMessage); + if (useMozAfterPaint) + content.messageManager.addMessageListener('PageLoader:MozAfterPaint', plPaintHandler); + removeLastAddedMsgListener = function() { + content.messageManager.removeMessageListener('PageLoader:Load', plLoadHandlerMessage); + content.messageManager.removeMessageListener('PageLoader:RecordTime', plRecordTimeMessage); + if (useMozAfterPaint) + content.messageManager.removeMessageListener('PageLoader:MozAfterPaint', plPaintHandler); + }; + } + + if (timeout > 0) { + timeoutEvent = setTimeout('loadFail()', timeout); + } + if (reportRSS) { + collectMemory(startAndLoadURI, pageName); + } else { + startAndLoadURI(pageName); + } +} + +function startAndLoadURI(pageName) { + start_time = Date.now(); + content.loadURI(pageName); +} + +function loadFail() { + var pageName = pages[pageIndex].url.spec; + dumpLine("__FAILTimeout exceeded on " + pageName + "__FAIL") + plStop(true); +} + +function plNextPage() { + var doNextPage = false; + if (pageCycle < numPageCycles) { + pageCycle++; + doNextPage = true; + } else if (pageIndex < pages.length-1) { + pageIndex++; + recordedName = null; + pageCycle = 1; + doNextPage = true; + } + + if (doNextPage == true) { + if (forceCC) { + var tccstart = new Date(); + window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils) + .garbageCollect(); + var tccend = new Date(); + report.recordCCTime(tccend - tccstart); + } + + setTimeout(plLoadPage, delay); + } else { + plStop(false); + } +} + +function plRecordTime(time) { + var pageName = pages[pageIndex].url.spec; + var i = pageIndex + if (i < pages.length-1) { + i++; + } else { + i = 0; + } + var nextName = pages[i].url.spec; + if (!recordedName) { + recordedName = pageUrls[pageIndex]; + } + if (typeof(time) == "string") { + var times = time.split(','); + var names = recordedName.split(','); + for (var t = 0; t < times.length; t++) { + if (names.length == 1) { + report.recordTime(names, times[t]); + } else { + report.recordTime(names[t], times[t]); + } + } + } else { + report.recordTime(recordedName, time); + } + if (noisy) { + dumpLine("Cycle " + (cycle+1) + "(" + pageCycle + ")" + ": loaded " + pageName + " (next: " + nextName + ")"); + } +} + +function plLoadHandlerCapturing(evt) { + // make sure we pick up the right load event + if (evt.type != 'load' || + evt.originalTarget.defaultView.frameElement) + return; + + //set the tpRecordTime function (called from test pages we load to store a global time. + content.contentWindow.wrappedJSObject.tpRecordTime = function (time, startTime, testName) { + gTime = time; + gStartTime = startTime; + recordedName = testName; + setTimeout(plWaitForPaintingCapturing, 0); + } + + content.removeEventListener('load', plLoadHandlerCapturing, true); + + setTimeout(plWaitForPaintingCapturing, 0); +} + +function plWaitForPaintingCapturing() { + if (gPaintListener) + return; + + var utils = gPaintWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + + if (utils.isMozAfterPaintPending && useMozAfterPaint) { + if (gPaintListener == false) + gPaintWindow.addEventListener("MozAfterPaint", plPaintedCapturing, true); + gPaintListener = true; + return; + } + + _loadHandlerCapturing(); +} + +function plPaintedCapturing() { + gPaintWindow.removeEventListener("MozAfterPaint", plPaintedCapturing, true); + gPaintListener = false; + _loadHandlerCapturing(); +} + +function _loadHandlerCapturing() { + if (timeout > 0) { + clearTimeout(timeoutEvent); + } + + if (!(plPageFlags() & TEST_DOES_OWN_TIMING)) { + dumpLine("tp: Capturing onload handler used with page that doesn't do its own timing?"); + plStop(true); + } + + if (useMozAfterPaint) { + if (gStartTime != null && gStartTime >= 0) { + gTime = (new Date()) - gStartTime; + gStartTime = -1; + } + } + + // set up the function for content to call + if (gTime != -1) { + plRecordTime(gTime); + gTime = -1; + recordedName = null; + setTimeout(plNextPage, delay); + }; +} + +// the onload handler +function plLoadHandler(evt) { + // make sure we pick up the right load event + if (evt.type != 'load' || + evt.originalTarget.defaultView.frameElement) + return; + + content.removeEventListener('load', plLoadHandler, true); + setTimeout(waitForPainted, 0); +} + +// This is called after we have received a load event, now we wait for painted +function waitForPainted() { + + var utils = gPaintWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + + if (!utils.isMozAfterPaintPending || !useMozAfterPaint) { + _loadHandler(); + return; + } + + if (gPaintListener == false) + gPaintWindow.addEventListener("MozAfterPaint", plPainted, true); + gPaintListener = true; +} + +function plPainted() { + gPaintWindow.removeEventListener("MozAfterPaint", plPainted, true); + gPaintListener = false; + _loadHandler(); +} + +function _loadHandler() { + if (timeout > 0) { + clearTimeout(timeoutEvent); + } + var docElem; + if (browserWindow) + docElem = browserWindow.frames["content"].document.documentElement; + else + docElem = content.contentDocument.documentElement; + var width; + if ("getBoundingClientRect" in docElem) { + width = docElem.getBoundingClientRect().width; + } else if ("offsetWidth" in docElem) { + width = docElem.offsetWidth; + } + + var end_time = Date.now(); + var time = (end_time - start_time); + + // does this page want to do its own timing? + // if so, we shouldn't be here + if (plPageFlags() & TEST_DOES_OWN_TIMING) { + dumpLine("tp: Bubbling onload handler used with page that does its own timing?"); + plStop(true); + } + + plRecordTime(time); + + if (doRenderTest) + runRenderTest(); + + plNextPage(); +} + +// the onload handler used for remote (e10s) browser +function plLoadHandlerMessage(message) { + _loadHandlerMessage(); +} + +// the mozafterpaint handler for remote (e10s) browser +function plPaintHandler(message) { + _loadHandlerMessage(); +} + +// the core handler for remote (e10s) browser +function _loadHandlerMessage() { + if (timeout > 0) { + clearTimeout(timeoutEvent); + } + + var time = -1; + + // does this page want to do its own timing? + if ((plPageFlags() & TEST_DOES_OWN_TIMING)) { + if (typeof(gStartTime) != "number") + gStartTime = Date.parse(gStartTime); + + if (gTime >= 0) { + if (useMozAfterPaint && gStartTime >= 0) { + gTime = Date.now() - gStartTime; + gStartTime = -1; + } else if (useMozAfterPaint) { + gTime = -1; + } + time = gTime; + gTime = -1; + } + + } else { + var end_time = Date.now(); + time = (end_time - start_time); + } + + if (time >= 0) { + plRecordTime(time); + if (doRenderTest) + runRenderTest(); + + plNextPage(); + } +} + +// the record time handler used for remote (e10s) browser +function plRecordTimeMessage(message) { + gTime = message.json.time; + if (useMozAfterPaint) { + gStartTime = message.json.startTime; + } + _loadHandlerMessage(); +} + +function runRenderTest() { + const redrawsPerSample = 500; + + if (!Ci.nsIDOMWindowUtils) + return; + + var win; + + if (browserWindow) + win = content.contentWindow; + else + win = window; + var wu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + + var start = Date.now(); + for (var j = 0; j < redrawsPerSample; j++) + wu.redraw(); + var end = Date.now(); + + renderReport.recordTime(pageIndex, end - start); +} + +function plStop(force) { + if (reportRSS) { + collectMemory(plStopAll, force); + } else { + plStopAll(force); + } +} + +function plStopAll(force) { + try { + if (force == false) { + pageIndex = 0; + pageCycle = 1; + if (cycle < NUM_CYCLES-1) { + cycle++; + recordedName = null; + setTimeout(plLoadPage, delay); + return; + } + + /* output report */ + dumpLine(report.getReport()); + } + } catch (e) { + dumpLine(e); + } + + if (reportRSS) { + stopMemCollector(); + } + + if (content) { + content.removeEventListener('load', plLoadHandlerCapturing, true); + content.removeEventListener('load', plLoadHandler, true); + if (useMozAfterPaint) + content.removeEventListener("MozAfterPaint", plPaintedCapturing, true); + content.removeEventListener("MozAfterPaint", plPainted, true); + + if (content.getAttribute("remote") == "true") { + content.messageManager.removeMessageListener('PageLoader:Load', plLoadHandlerMessage); + content.messageManager.removeMessageListener('PageLoader:RecordTime', plRecordTimeMessage); + if (useMozAfterPaint) + content.messageManager.removeMessageListener('PageLoader:MozAfterPaint', plPaintHandler); + + content.messageManager.loadFrameScript("data:,removeEventListener('load', _contentLoadHandler, true);", false); + } + } + + if (MozillaFileLogger) + MozillaFileLogger.close(); + + goQuitApplication(); +} + +/* Returns array */ +function plLoadURLsFromURI(manifestUri) { + var fstream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + var uriFile = manifestUri.QueryInterface(Ci.nsIFileURL); + + fstream.init(uriFile.file, -1, 0, 0); + var lstream = fstream.QueryInterface(Ci.nsILineInputStream); + + var d = []; + + var lineNo = 0; + var line = {value:null}; + var more; + do { + lineNo++; + more = lstream.readLine(line); + var s = line.value; + + // strip comments + s = s.replace(/#.*/, ''); + + // strip leading and trailing whitespace + s = s.replace(/^\s*/, '').replace(/\s*$/, ''); + + if (!s) + continue; + + var flags = 0; + var urlspec = s; + + // split on whitespace, and figure out if we have any flags + var items = s.split(/\s+/); + if (items[0] == "include") { + if (items.length != 2) { + dumpLine("tp: Error on line " + lineNo + " in " + manifestUri.spec + ": include must be followed by the manifest to include!"); + return null; + } + + var subManifest = gIOS.newURI(items[1], null, manifestUri); + if (subManifest == null) { + dumpLine("tp: invalid URI on line " + manifestUri.spec + ":" + lineNo + " : '" + line.value + "'"); + return null; + } + + var subItems = plLoadURLsFromURI(subManifest); + if (subItems == null) + return null; + d = d.concat(subItems); + } else { + if (items.length == 2) { + if (items[0].indexOf("%") != -1) + flags |= TEST_DOES_OWN_TIMING; + + urlspec = items[1]; + } else if (items.length != 1) { + dumpLine("tp: Error on line " + lineNo + " in " + manifestUri.spec + ": whitespace must be %-escaped!"); + return null; + } + + var url = gIOS.newURI(urlspec, null, manifestUri); + + if (pageFilterRegexp && !pageFilterRegexp.test(url.spec)) + continue; + + d.push({ url: url, + flags: flags }); + } + } while (more); + + return d; +} + +function dumpLine(str) { + if (MozillaFileLogger) + MozillaFileLogger.log(str + "\n"); + dump(str); + dump("\n"); +} diff --git a/pageloader/chrome/pageloader.xul b/pageloader/chrome/pageloader.xul new file mode 100644 index 0000000..af2a8d1 --- /dev/null +++ b/pageloader/chrome/pageloader.xul @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + diff --git a/pageloader/chrome/quit.js b/pageloader/chrome/quit.js new file mode 100644 index 0000000..9a212d6 --- /dev/null +++ b/pageloader/chrome/quit.js @@ -0,0 +1,110 @@ +/* -*- 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 Original Code is 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 + * + * 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) + { + } + return true; +} + +function goQuitApplication() +{ + 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'; + } + + try + { + appService.quit(forceQuit); + } + catch(ex) + { + throw('goQuitApplication: ' + ex); + } + + return true; +} + diff --git a/pageloader/chrome/report.js b/pageloader/chrome/report.js new file mode 100644 index 0000000..73f58c3 --- /dev/null +++ b/pageloader/chrome/report.js @@ -0,0 +1,94 @@ +/* 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/. */ + +// given an array of strings, finds the longest common prefix +function findCommonPrefixLength(strs) { + if (strs.length < 2) + return 0; + + var len = 0; + do { + var newlen = len + 1; + var newprefix = null; + var failed = false; + for (var i = 0; i < strs.length; i++) { + if (newlen > strs[i].length) { + failed = true; + break; + } + + var s = strs[i].substr(0, newlen); + if (newprefix == null) { + newprefix = s; + } else if (newprefix != s) { + failed = true; + break; + } + } + + if (failed) + break; + + len++; + } while (true); + return len; +} + +// Constructor +function Report() { + this.timeVals = {}; + this.totalCCTime = 0; + this.showTotalCCTime = false; +} + +Report.prototype.pageNames = function() { + var retval = new Array(); + for (var page in this.timeVals) { + retval.push(page); + } + return retval; +} + +Report.prototype.getReport = function() { + + var report; + var pages = this.pageNames(); + var prefixLen = findCommonPrefixLength(pages); + + report = "__start_tp_report\n"; + report += "_x_x_mozilla_page_load\n"; + report += "_x_x_mozilla_page_load_details\n"; + report += "|i|pagename|runs|\n"; + + for (var i=0; i < pages.length; i++) { + report += '|'+ + i + ';'+ + pages[i].substr(prefixLen) + ';'+ + this.timeVals[pages[i]].join(";") + + "\n"; + } + report += "__end_tp_report\n"; + + if (this.showTotalCCTime) { + report += "__start_cc_report\n"; + report += "_x_x_mozilla_cycle_collect," + this.totalCCTime + "\n"; + report += "__end_cc_report\n"; + } + var now = (new Date()).getTime(); + report += "__startTimestamp" + now + "__endTimestamp\n"; //timestamp for determning shutdown time, used by talos + + return report; +} + +Report.prototype.recordTime = function(pageName, ms) { + if (this.timeVals[pageName] == undefined) { + this.timeVals[pageName] = new Array(); + } + this.timeVals[pageName].push(ms); +} + +Report.prototype.recordCCTime = function(ms) { + this.totalCCTime += ms; + this.showTotalCCTime = true; +} diff --git a/pageloader/components/tp-cmdline.js b/pageloader/components/tp-cmdline.js new file mode 100644 index 0000000..a5a7225 --- /dev/null +++ b/pageloader/components/tp-cmdline.js @@ -0,0 +1,197 @@ +/* ***** 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 DOM Inspector. + * + * The Initial Developer of the Original Code is + * Christopher A. Aillon . + * Portions created by the Initial Developer are Copyright (C) 2003 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Christopher A. Aillon + * L. David Baron, Mozilla Corporation (modified for reftest) + * Vladimir Vukicevic, Mozilla Corporation (modified for tp) + * + * 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 ***** */ + +// This only implements nsICommandLineHandler, since it needs +// to handle multiple arguments. + +const TP_CMDLINE_CONTRACTID = "@mozilla.org/commandlinehandler/general-startup;1?type=tp"; +const TP_CMDLINE_CLSID = Components.ID('{8AF052F5-8EFE-4359-8266-E16498A82E8B}'); +const CATMAN_CONTRACTID = "@mozilla.org/categorymanager;1"; +const nsISupports = Components.interfaces.nsISupports; + +const nsICategoryManager = Components.interfaces.nsICategoryManager; +const nsICommandLine = Components.interfaces.nsICommandLine; +const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler; +const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar; +const nsISupportsString = Components.interfaces.nsISupportsString; +const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher; + +function PageLoaderCmdLineHandler() {} +PageLoaderCmdLineHandler.prototype = +{ + /* nsISupports */ + QueryInterface : function handler_QI(iid) { + if (iid.equals(nsISupports)) + return this; + + if (nsICommandLineHandler && iid.equals(nsICommandLineHandler)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + /* nsICommandLineHandler */ + handle : function handler_handle(cmdLine) { + var args = {}; + try { + var uristr = cmdLine.handleFlagWithParam("tp", false); + if (uristr == null) + return; + try { + args.manifest = cmdLine.resolveURI(uristr).spec; + } catch (e) { + return; + } + + args.numCycles = cmdLine.handleFlagWithParam("tpcycles", false); + args.numPageCycles = cmdLine.handleFlagWithParam("tppagecycles", false); + args.startIndex = cmdLine.handleFlagWithParam("tpstart", false); + args.endIndex = cmdLine.handleFlagWithParam("tpend", false); + args.filter = cmdLine.handleFlagWithParam("tpfilter", false); + args.useBrowserChrome = cmdLine.handleFlag("tpchrome", false); + args.doRender = cmdLine.handleFlag("tprender", false); + args.width = cmdLine.handleFlagWithParam("tpwidth", false); + args.height = cmdLine.handleFlagWithParam("tpheight", false); + args.offline = cmdLine.handleFlag("tpoffline", false); + args.noisy = cmdLine.handleFlag("tpnoisy", false); + args.timeout = cmdLine.handleFlagWithParam("tptimeout", false); + args.delay = cmdLine.handleFlagWithParam("tpdelay", false); + args.noForceCC = cmdLine.handleFlag("tpnoforcecc", false); + args.mozafterpaint = cmdLine.handleFlag("tpmozafterpaint", false); + args.rss = cmdLine.handleFlag("rss", false); + } + catch (e) { + return; + } + + // get our data through xpconnect + args.wrappedJSObject = args; + + var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] + .getService(nsIWindowWatcher); + wwatch.openWindow(null, "chrome://pageloader/content/pageloader.xul", "_blank", + "chrome,dialog=no,all", args); + cmdLine.preventDefault = true; + }, + + helpInfo : + " -tp Run pageload perf tests on given manifest\n" + + " -tpfilter str Only include pages from manifest that contain str (regexp)\n" + + " -tpcycles n Loop through pages n times\n" + + " -tppagecycles n Loop through each page n times before going onto the next page\n" + + " -tpstart n Start at index n in the manifest\n" + + " -tpend n End with index n in the manifest\n" + + " -tpchrome Test with normal browser chrome\n" + + " -tprender Run render-only benchmark for each page\n" + + " -tpwidth width Width of window\n" + + " -tpheight height Height of window\n" + + " -tpoffline Force offline mode\n" + + " -tpnoisy Dump the name of the last loaded page to console\n" + + " -tptimeout Max amount of time given for a page to load, quit if exceeded\n" + + " -tpdelay Amount of time to wait between each pageload\n" + + " -tpnoforcecc Don't force cycle collection between each pageload\n" + + " -tpmozafterpaint Measure Time after recieving MozAfterPaint event instead of load event\n" + + " -rss Dump RSS after each page is loaded\n" + +}; + + +var PageLoaderCmdLineFactory = +{ + createInstance : function(outer, iid) + { + if (outer != null) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + + return new PageLoaderCmdLineHandler().QueryInterface(iid); + } +}; + +function NSGetFactory(cid) { + if (!cid.equals(TP_CMDLINE_CLSID)) + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + + return PageLoaderCmdLineFactory; +} + +var PageLoaderCmdLineModule = +{ + registerSelf : function(compMgr, fileSpec, location, type) + { + compMgr = compMgr.QueryInterface(nsIComponentRegistrar); + + compMgr.registerFactoryLocation(TP_CMDLINE_CLSID, + "PageLoader CommandLine Service", + TP_CMDLINE_CONTRACTID, + fileSpec, + location, + type); + + var catman = Components.classes[CATMAN_CONTRACTID].getService(nsICategoryManager); + catman.addCategoryEntry("command-line-handler", + "m-tp", + TP_CMDLINE_CONTRACTID, true, true); + }, + + unregisterSelf : function(compMgr, fileSpec, location) + { + compMgr = compMgr.QueryInterface(nsIComponentRegistrar); + + compMgr.unregisterFactoryLocation(TP_CMDLINE_CLSID, fileSpec); + catman = Components.classes[CATMAN_CONTRACTID].getService(nsICategoryManager); + catman.deleteCategoryEntry("command-line-handler", + "m-tp", true); + }, + + getClassObject : function(compMgr, cid, iid) + { + return NSGetFactory(cid); + }, + + canUnload : function(compMgr) + { + return true; + } +}; + + +function NSGetModule(compMgr, fileSpec) { + return PageLoaderCmdLineModule; +} diff --git a/pageloader/install.rdf b/pageloader/install.rdf new file mode 100644 index 0000000..6f005f2 --- /dev/null +++ b/pageloader/install.rdf @@ -0,0 +1,20 @@ + + + + + pageloader@mozilla.org + 1.0 + + + toolkit@mozilla.org + 2.0b3pre + * + + + + PageLoader extension + Cycles through pages and measures load times + Vladimir Vukicevic + +