@ -1,11 +1,28 @@
|
||||
declare var exportFunction;
|
||||
declare var unsafeWindow;
|
||||
|
||||
exportFunction(function() { (<any>self).port.emit('startProfiler'); }, unsafeWindow,
|
||||
{defineAs: "startProfiler"});
|
||||
exportFunction(function() {
|
||||
var curTime = unsafeWindow.performance.now();
|
||||
(<any>self).port.emit('startProfiler', curTime);
|
||||
}, unsafeWindow, {defineAs: "startProfiler"});
|
||||
|
||||
exportFunction(function(filePath) { (<any>self).port.emit('stopAndRecord', filePath); },
|
||||
unsafeWindow, {defineAs: "stopAndRecord"});
|
||||
exportFunction(function() { (<any>self).port.emit('stopProfiler'); }, unsafeWindow,
|
||||
{defineAs: "stopProfiler"});
|
||||
|
||||
exportFunction(function(cb) {
|
||||
(<any>self).port.once('perfProfile', cb);
|
||||
(<any>self).port.emit('getProfile');
|
||||
}, unsafeWindow, {defineAs: "getProfile"});
|
||||
|
||||
exportFunction(function() { (<any>self).port.emit('forceGC'); }, unsafeWindow,
|
||||
{defineAs: "forceGC"});
|
||||
|
||||
exportFunction(function(name) {
|
||||
var curTime = unsafeWindow.performance.now();
|
||||
(<any>self).port.emit('markStart', name, curTime);
|
||||
}, unsafeWindow, {defineAs: "markStart"});
|
||||
|
||||
exportFunction(function(name) {
|
||||
var curTime = unsafeWindow.performance.now();
|
||||
(<any>self).port.emit('markEnd', name, curTime);
|
||||
}, unsafeWindow, {defineAs: "markEnd"});
|
||||
|
@ -1,52 +1,66 @@
|
||||
/// <reference path="../../../../angular2/typings/node/node.d.ts" />
|
||||
|
||||
var file = require('sdk/io/file');
|
||||
var {Cc, Ci, Cu} = require("chrome");
|
||||
var {Cc, Ci, Cu} = require('chrome');
|
||||
var os = Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
|
||||
var ParserUtil = require('./parser_util');
|
||||
|
||||
class Profiler {
|
||||
private _profiler;
|
||||
constructor() { this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); }
|
||||
private _markerEvents: List<any>;
|
||||
private _profilerStartTime: number;
|
||||
|
||||
start(entries, interval, features) {
|
||||
constructor() { this._profiler = Cc['@mozilla.org/tools/profiler;1'].getService(Ci.nsIProfiler); }
|
||||
|
||||
start(entries, interval, features, timeStarted) {
|
||||
this._profiler.StartProfiler(entries, interval, features, features.length);
|
||||
this._profilerStartTime = timeStarted;
|
||||
this._markerEvents = [];
|
||||
}
|
||||
|
||||
stop() { this._profiler.StopProfiler(); }
|
||||
getProfileData() { return this._profiler.getProfileData(); }
|
||||
}
|
||||
|
||||
getProfilePerfEvents() {
|
||||
var profileData = this._profiler.getProfileData();
|
||||
var perfEvents = ParserUtil.convertPerfProfileToEvents(profileData);
|
||||
perfEvents = this._mergeMarkerEvents(perfEvents);
|
||||
perfEvents.sort(function(event1, event2) { return event1.ts - event2.ts; }); // Sort by ts
|
||||
return perfEvents;
|
||||
}
|
||||
|
||||
function saveToFile(savePath: string, body: string) {
|
||||
var textWriter = file.open(savePath, 'w');
|
||||
textWriter.write(body);
|
||||
textWriter.close();
|
||||
_mergeMarkerEvents(perfEvents: List<any>): List<any> {
|
||||
this._markerEvents.forEach(function(markerEvent) { perfEvents.push(markerEvent); });
|
||||
return perfEvents;
|
||||
}
|
||||
|
||||
addStartEvent(name: string, timeStarted: number) {
|
||||
this._markerEvents.push({ph: 'b', ts: timeStarted - this._profilerStartTime, name: name});
|
||||
}
|
||||
|
||||
addEndEvent(name: string, timeEnded: number) {
|
||||
this._markerEvents.push({ph: 'e', ts: timeEnded - this._profilerStartTime, name: name});
|
||||
}
|
||||
}
|
||||
|
||||
function forceGC() {
|
||||
Cu.forceGC();
|
||||
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
|
||||
os.notifyObservers(null, "child-gc-request", null);
|
||||
}
|
||||
os.notifyObservers(null, 'child-gc-request', null);
|
||||
};
|
||||
|
||||
var mod = require('sdk/page-mod');
|
||||
var data = require('sdk/self').data;
|
||||
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"),
|
||||
contentScriptFile: data.url('installed_script.js'),
|
||||
onAttach: worker => {
|
||||
worker.port.on('startProfiler', () => startProfiler());
|
||||
worker.port.on('stopAndRecord', filePath => stopAndRecord(filePath));
|
||||
worker.port.on('forceGC', () => forceGC());
|
||||
worker.port.on('startProfiler',
|
||||
(timeStarted) => profiler.start(/* = profiler memory */ 1000000, 1,
|
||||
['leaf', 'js', 'stackwalk', 'gc'], timeStarted));
|
||||
worker.port.on('stopProfiler', () => profiler.stop());
|
||||
worker.port.on('getProfile',
|
||||
() => worker.port.emit('perfProfile', profiler.getProfilePerfEvents()));
|
||||
worker.port.on('forceGC', forceGC);
|
||||
worker.port.on('markStart', (name, timeStarted) => profiler.addStartEvent(name, timeStarted));
|
||||
worker.port.on('markEnd', (name, timeEnded) => profiler.addEndEvent(name, timeEnded));
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,2 @@
|
||||
library benchpress.src.firefox_extension.lib.parser_util;
|
||||
//no dart implementation
|
84
modules/benchpress/src/firefox_extension/lib/parser_util.ts
Normal file
84
modules/benchpress/src/firefox_extension/lib/parser_util.ts
Normal file
@ -0,0 +1,84 @@
|
||||
/// <reference path="../../../../angular2/typings/node/node.d.ts" />
|
||||
|
||||
/**
|
||||
* @param {Object} perfProfile The perf profile JSON object.
|
||||
* @return {Array<Object>} An array of recognized events that are captured
|
||||
* within the perf profile.
|
||||
*/
|
||||
export function convertPerfProfileToEvents(perfProfile: any): List<any> {
|
||||
var inProgressEvents = new Map(); // map from event name to start time
|
||||
var finishedEvents = []; // Array<Event> finished events
|
||||
var addFinishedEvent = function(eventName, startTime, endTime) {
|
||||
var categorizedEventName = categorizeEvent(eventName);
|
||||
var args = undefined;
|
||||
if (categorizedEventName == 'gc') {
|
||||
// TODO: We cannot measure heap size at the moment
|
||||
args = {usedHeapSize: 0};
|
||||
}
|
||||
if (startTime == endTime) {
|
||||
// Finished instantly
|
||||
finishedEvents.push({ph: 'X', ts: startTime, name: categorizedEventName, args: args});
|
||||
} else {
|
||||
// Has duration
|
||||
finishedEvents.push({ph: 'B', ts: startTime, name: categorizedEventName, args: args});
|
||||
finishedEvents.push({ph: 'E', ts: endTime, name: categorizedEventName, args: args});
|
||||
}
|
||||
};
|
||||
|
||||
var samples = perfProfile.threads[0].samples;
|
||||
// In perf profile, firefox samples all the frames in set time intervals. Here
|
||||
// we go through all the samples and construct the start and end time for each
|
||||
// event.
|
||||
for (var i = 0; i < samples.length; ++i) {
|
||||
var sample = samples[i];
|
||||
var sampleTime = sample.time;
|
||||
|
||||
// Add all the frames into a set so it's easier/faster to find the set
|
||||
// differences
|
||||
var sampleFrames = new Set();
|
||||
sample.frames.forEach(function(frame) { sampleFrames.add(frame.location); });
|
||||
|
||||
// If an event is in the inProgressEvents map, but not in the current sample,
|
||||
// then it must have just finished. We add this event to the finishedEvents
|
||||
// array and remove it from the inProgressEvents map.
|
||||
var previousSampleTime = (i == 0 ? /* not used */ -1 : samples[i - 1].time);
|
||||
inProgressEvents.forEach(function(startTime, eventName) {
|
||||
if (!(sampleFrames.has(eventName))) {
|
||||
addFinishedEvent(eventName, startTime, previousSampleTime);
|
||||
inProgressEvents.delete(eventName);
|
||||
}
|
||||
});
|
||||
|
||||
// If an event is in the current sample, but not in the inProgressEvents map,
|
||||
// then it must have just started. We add this event to the inProgressEvents
|
||||
// map.
|
||||
sampleFrames.forEach(function(eventName) {
|
||||
if (!(inProgressEvents.has(eventName))) {
|
||||
inProgressEvents.set(eventName, sampleTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If anything is still in progress, we need to included it as a finished event
|
||||
// since recording ended.
|
||||
var lastSampleTime = samples[samples.length - 1].time;
|
||||
inProgressEvents.forEach(function(startTime, eventName) {
|
||||
addFinishedEvent(eventName, startTime, lastSampleTime);
|
||||
});
|
||||
|
||||
// Remove all the unknown categories.
|
||||
return finishedEvents.filter(function(event) { return event.name != 'unknown'; });
|
||||
}
|
||||
|
||||
// TODO: this is most likely not exhaustive.
|
||||
export function categorizeEvent(eventName: string): string {
|
||||
if (eventName.indexOf('PresShell::Paint') > -1) {
|
||||
return 'render';
|
||||
} else if (eventName.indexOf('FirefoxDriver.prototype.executeScript') > -1) {
|
||||
return 'script';
|
||||
} else if (eventName.indexOf('forceGC') > -1) {
|
||||
return 'gc';
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
@ -1,5 +1 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"main": "lib/main.js",
|
||||
"name": "ffperf-addon"
|
||||
}
|
||||
{ "version" : "0.0.1", "main" : "lib/main.js", "name" : "ffperf-addon" }
|
||||
|
@ -12,6 +12,7 @@ import {Validator} from './validator';
|
||||
import {PerflogMetric} from './metric/perflog_metric';
|
||||
import {MultiMetric} from './metric/multi_metric';
|
||||
import {ChromeDriverExtension} from './webdriver/chrome_driver_extension';
|
||||
import {FirefoxDriverExtension} from './webdriver/firefox_driver_extension';
|
||||
import {IOsDriverExtension} from './webdriver/ios_driver_extension';
|
||||
import {WebDriverExtension} from './web_driver_extension';
|
||||
import {SampleDescription} from './sample_description';
|
||||
@ -62,6 +63,7 @@ var _DEFAULT_BINDINGS = [
|
||||
RegressionSlopeValidator.BINDINGS,
|
||||
SizeValidator.BINDINGS,
|
||||
ChromeDriverExtension.BINDINGS,
|
||||
FirefoxDriverExtension.BINDINGS,
|
||||
IOsDriverExtension.BINDINGS,
|
||||
PerflogMetric.BINDINGS,
|
||||
SampleDescription.BINDINGS,
|
||||
@ -70,7 +72,7 @@ var _DEFAULT_BINDINGS = [
|
||||
|
||||
Reporter.bindTo(MultiReporter),
|
||||
Validator.bindTo(RegressionSlopeValidator),
|
||||
WebDriverExtension.bindTo([ChromeDriverExtension, IOsDriverExtension]),
|
||||
WebDriverExtension.bindTo([ChromeDriverExtension, FirefoxDriverExtension, IOsDriverExtension]),
|
||||
Metric.bindTo(MultiMetric),
|
||||
|
||||
bind(Options.CAPABILITIES)
|
||||
|
@ -16,6 +16,7 @@ export class WebDriverAdapter {
|
||||
|
||||
waitFor(callback: Function): Promise<any> { throw new BaseException('NYI'); }
|
||||
executeScript(script: string): Promise<any> { throw new BaseException('NYI'); }
|
||||
executeAsyncScript(script: string): Promise<any> { throw new BaseException('NYI'); }
|
||||
capabilities(): Promise<Map<string, any>> { throw new BaseException('NYI'); }
|
||||
logs(type: string): Promise<List<any>> { throw new BaseException('NYI'); }
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ class AsyncWebDriverAdapter extends WebDriverAdapter {
|
||||
return _driver.execute(script, const []);
|
||||
}
|
||||
|
||||
Future executeAsyncScript(String script) {
|
||||
return _driver.executeAsync(script, const []);
|
||||
}
|
||||
|
||||
Future<Map> capabilities() {
|
||||
return new Future.value(_driver.capabilities);
|
||||
}
|
||||
|
49
modules/benchpress/src/webdriver/firefox_driver_extension.ts
Normal file
49
modules/benchpress/src/webdriver/firefox_driver_extension.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {bind, Binding} from 'angular2/di';
|
||||
import {isPresent, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {WebDriverExtension, PerfLogFeatures} from '../web_driver_extension';
|
||||
import {WebDriverAdapter} from '../web_driver_adapter';
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
|
||||
export class FirefoxDriverExtension extends WebDriverExtension {
|
||||
static get BINDINGS(): List<Binding> { return _BINDINGS; }
|
||||
|
||||
private _profilerStarted: boolean;
|
||||
|
||||
constructor(private _driver: WebDriverAdapter) {
|
||||
super();
|
||||
this._profilerStarted = false;
|
||||
}
|
||||
|
||||
gc() { return this._driver.executeScript('window.forceGC()'); }
|
||||
|
||||
timeBegin(name: string): Promise<any> {
|
||||
if (!this._profilerStarted) {
|
||||
this._profilerStarted = true;
|
||||
this._driver.executeScript('window.startProfiler();');
|
||||
}
|
||||
return this._driver.executeScript('window.markStart("' + name + '");');
|
||||
}
|
||||
|
||||
timeEnd(name: string, restartName: string = null): Promise<any> {
|
||||
var script = 'window.markEnd("' + name + '");';
|
||||
if (isPresent(restartName)) {
|
||||
script += 'window.markStart("' + restartName + '");';
|
||||
}
|
||||
return this._driver.executeScript(script);
|
||||
}
|
||||
|
||||
readPerfLog(): Promise<any> {
|
||||
return this._driver.executeAsyncScript('var cb = arguments[0]; window.getProfile(cb);');
|
||||
}
|
||||
|
||||
perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true, gc: true}); }
|
||||
|
||||
supports(capabilities: StringMap<string, any>): boolean {
|
||||
return StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'firefox');
|
||||
}
|
||||
}
|
||||
|
||||
var _BINDINGS = [
|
||||
bind(FirefoxDriverExtension)
|
||||
.toFactory((driver) => new FirefoxDriverExtension(driver), [WebDriverAdapter])
|
||||
];
|
@ -30,6 +30,10 @@ export class SeleniumWebDriverAdapter extends WebDriverAdapter {
|
||||
return this._convertPromise(this._driver.executeScript(script));
|
||||
}
|
||||
|
||||
executeAsyncScript(script: string): Promise<any> {
|
||||
return this._convertPromise(this._driver.executeAsyncScript(script));
|
||||
}
|
||||
|
||||
capabilities(): Promise<any> {
|
||||
return this._convertPromise(
|
||||
this._driver.getCapabilities().then((capsObject) => capsObject.toJSON()));
|
||||
|
Reference in New Issue
Block a user