From b390f441a1ae292aacb6a83fd37da64da6c0328b Mon Sep 17 00:00:00 2001 From: Hank Duan Date: Mon, 18 May 2015 18:10:30 -0700 Subject: [PATCH] feat(benchpress): Add extension for ff metrics reporting Closes #1976 --- modules/benchpress/package.json | 1 + .../src/firefox_extension/.gitignore | 2 + .../data/installed_script.es6 | 12 ++++ .../src/firefox_extension/lib/main.es6 | 61 +++++++++++++++++++ .../src/firefox_extension/lib/test_helper.es6 | 47 ++++++++++++++ .../src/firefox_extension/package.json | 5 ++ .../test/firefox_extension/conf.es6 | 18 ++++++ .../test/firefox_extension/spec.es6 | 50 +++++++++++++++ package.json | 1 + 9 files changed, 197 insertions(+) create mode 100644 modules/benchpress/src/firefox_extension/.gitignore create mode 100644 modules/benchpress/src/firefox_extension/data/installed_script.es6 create mode 100644 modules/benchpress/src/firefox_extension/lib/main.es6 create mode 100644 modules/benchpress/src/firefox_extension/lib/test_helper.es6 create mode 100644 modules/benchpress/src/firefox_extension/package.json create mode 100644 modules/benchpress/test/firefox_extension/conf.es6 create mode 100644 modules/benchpress/test/firefox_extension/spec.es6 diff --git a/modules/benchpress/package.json b/modules/benchpress/package.json index d8f07cf9e6..412d670c94 100644 --- a/modules/benchpress/package.json +++ b/modules/benchpress/package.json @@ -9,6 +9,7 @@ "license": "<%= packageJson.license %>", "dependencies": { "angular2": "<%= packageJson.version %>", + "firefox-profile": "<%= packageJson.dependencies['firefox-profile'] %>", "rtts_assert": "<%= packageJson.version %>", "traceur": "<%= packageJson.dependencies.traceur %>", "selenium-webdriver": "<%= packageJson.dependencies['selenium-webdriver'] %>" diff --git a/modules/benchpress/src/firefox_extension/.gitignore b/modules/benchpress/src/firefox_extension/.gitignore new file mode 100644 index 0000000000..fc1a520a40 --- /dev/null +++ b/modules/benchpress/src/firefox_extension/.gitignore @@ -0,0 +1,2 @@ +*.xpi +addon-sdk* diff --git a/modules/benchpress/src/firefox_extension/data/installed_script.es6 b/modules/benchpress/src/firefox_extension/data/installed_script.es6 new file mode 100644 index 0000000000..88cc250371 --- /dev/null +++ b/modules/benchpress/src/firefox_extension/data/installed_script.es6 @@ -0,0 +1,12 @@ +exportFunction(function() { + self.port.emit('startProfiler'); +}, unsafeWindow, {defineAs: "startProfiler"}); + +exportFunction(function(filePath) { + self.port.emit('stopAndRecord', filePath); +}, unsafeWindow, {defineAs: "stopAndRecord"}); + +exportFunction(function() { + self.port.emit('forceGC'); +}, unsafeWindow, {defineAs: "forceGC"}); + diff --git a/modules/benchpress/src/firefox_extension/lib/main.es6 b/modules/benchpress/src/firefox_extension/lib/main.es6 new file mode 100644 index 0000000000..ecfb56488f --- /dev/null +++ b/modules/benchpress/src/firefox_extension/lib/main.es6 @@ -0,0 +1,61 @@ +var file = require('sdk/io/file'); +var {Cc,Ci,Cu} = require("chrome"); + +function Profiler() { + this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); +}; + +Profiler.prototype = { + start: function(entries, interval, features) { + this._profiler.StartProfiler(entries, interval, features, features.length); + }, + stop: function(cb) { + this._profiler.StopProfiler(); + // this.gcStats.clear(); + }, + getProfileData: function() { + return this._profiler.getProfileData(); + } +}; + +function saveToFile(savePath, str) { + var textWriter = file.open(savePath, 'w'); + textWriter.write(str); + textWriter.close(); +} + +function forceGC() { + Cu.forceGC(); + var os = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + os.notifyObservers(null, "child-gc-request", null); +} + +var profiler = new Profiler(); +function startProfiler() { + profiler.start(/* = profiler memory */ 10000000, 1, ['leaf', 'js', "stackwalk", 'gc']); +}; +function stopAndRecord(filePath) { + var profileData = profiler.getProfileData(); + profiler.stop(); + saveToFile(filePath, JSON.stringify(profileData, null, 2)); +}; + + +var mod = require("sdk/page-mod"); +var data = require("sdk/self").data; +mod.PageMod({ + include: ['*'], + contentScriptFile: data.url("installed_script.js"), + onAttach: function(worker) { + worker.port.on('startProfiler', function() { + startProfiler(); + }); + worker.port.on('stopAndRecord', function(filePath) { + stopAndRecord(filePath); + }); + worker.port.on('forceGC', function() { + forceGC(); + }); + } +}); diff --git a/modules/benchpress/src/firefox_extension/lib/test_helper.es6 b/modules/benchpress/src/firefox_extension/lib/test_helper.es6 new file mode 100644 index 0000000000..ee3eea4bc1 --- /dev/null +++ b/modules/benchpress/src/firefox_extension/lib/test_helper.es6 @@ -0,0 +1,47 @@ +var q = require('q'); +var FirefoxProfile = require('firefox-profile'); +var jpm = require('jpm/lib/xpi'); +var pathUtil = require('path'); + +var PERF_ADDON_PACKAGE_JSON_DIR = '..'; + +exports.getAbsolutePath = function(path) { + var normalizedPath = pathUtil.normalize(path); + if (pathUtil.resolve(normalizedPath) == normalizedPath) { + // Already absolute path + return normalizedPath; + } else { + return pathUtil.join(__dirname, normalizedPath); + } +}; + +exports.getFirefoxProfile = function(extensionPath) { + var deferred = q.defer(); + + var firefoxProfile = new FirefoxProfile(); + firefoxProfile.addExtensions([extensionPath], function() { + firefoxProfile.encoded(function(encodedProfile) { + var multiCapabilities = [{ + browserName: 'firefox', + firefox_profile : encodedProfile + }]; + deferred.resolve(multiCapabilities); + }); + }); + + return deferred.promise; +}; + +exports.getFirefoxProfileWithExtension = function() { + var absPackageJsonDir = pathUtil.join(__dirname, PERF_ADDON_PACKAGE_JSON_DIR); + var packageJson = require(pathUtil.join(absPackageJsonDir, 'package.json')); + + var savedCwd = process.cwd(); + process.chdir(absPackageJsonDir); + + return jpm(packageJson).then(function(xpiPath) { + process.chdir(savedCwd); + return exports.getFirefoxProfile(xpiPath); + }); +}; + diff --git a/modules/benchpress/src/firefox_extension/package.json b/modules/benchpress/src/firefox_extension/package.json new file mode 100644 index 0000000000..135e7a9877 --- /dev/null +++ b/modules/benchpress/src/firefox_extension/package.json @@ -0,0 +1,5 @@ +{ + "version": "0.0.1", + "main": "lib/main.js", + "name": "ffperf-addon" +} diff --git a/modules/benchpress/test/firefox_extension/conf.es6 b/modules/benchpress/test/firefox_extension/conf.es6 new file mode 100644 index 0000000000..057cd936c2 --- /dev/null +++ b/modules/benchpress/test/firefox_extension/conf.es6 @@ -0,0 +1,18 @@ +var testHelper = require('../../src/firefox_extension/lib/test_helper.js'); + +// Where to save profile results (parent folder must exist) +var PROFILE_SAVE_PATH = './perfProfile.json'; + +exports.config = { + seleniumAddress: 'http://127.0.0.1:4444/wd/hub', + + specs: ['spec.js'], + + getMultiCapabilities: function() { + return testHelper.getFirefoxProfileWithExtension(); + }, + + params: { + profileSavePath: testHelper.getAbsolutePath(PROFILE_SAVE_PATH) + } +}; diff --git a/modules/benchpress/test/firefox_extension/spec.es6 b/modules/benchpress/test/firefox_extension/spec.es6 new file mode 100644 index 0000000000..faddd525e9 --- /dev/null +++ b/modules/benchpress/test/firefox_extension/spec.es6 @@ -0,0 +1,50 @@ +var fs = require('fs'); + +var validateFile = function() { + try { + var content = fs.readFileSync(browser.params.profileSavePath, 'utf8'); + // TODO(hankduan): This check not very useful. Ideally we want to validate + // that the file contains all the events that we are looking for. Pending + // on data transformer. + expect(content).toContain('forceGC'); + // Delete file + fs.unlinkSync(browser.params.profileSavePath); + } catch (err) { + if (err.code === 'ENOENT') { + // If files doesn't exist + console.error('Error: firefox extension did not save profile JSON'); + } else { + console.error('Error: ' + err); + } + throw err; + } +}; + +describe('firefox extension', function() { + it ('should measure performance', function() { + browser.sleep(3000); // wait for extension to load + + browser.driver.get('http://www.angularjs.org'); + + browser.executeScript('window.startProfiler()').then(function() { + console.log('started measuring perf'); + }); + + browser.executeScript('window.forceGC()'); + + // Run some commands + element(by.model('yourName')).sendKeys('Hank'); + expect(element(by.binding('yourName')).getText()).toEqual('Hello Hank!'); + + browser.executeScript('window.forceGC()'); + + var script = + 'window.stopAndRecord("' + browser.params.profileSavePath + '")'; + browser.executeScript(script).then(function() { + console.log('stopped measuring perf'); + }); + + // wait for it to finish, then validate file. + browser.sleep(3000).then(validateFile); + }) +}); diff --git a/package.json b/package.json index 85b2c5e6e3..353aa5c4d1 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "es6-module-loader": "^0.9.2", + "firefox-profile": "0.3.4", "googleapis": "1.0.x", "gulp-insert": "^0.4.0", "gulp-modify": "0.0.5",