Skip to content

Commit 0949a4b

Browse files
hankduantbosch
authored andcommitted
feat(benchpress): initial support for firefox
Closes angular#2419
1 parent 7a4a3c8 commit 0949a4b

18 files changed

+380
-69
lines changed

modules/benchpress/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export {JsonFileReporter} from './src/reporter/json_file_reporter';
1111
export {SampleDescription} from './src/sample_description';
1212
export {PerflogMetric} from './src/metric/perflog_metric';
1313
export {ChromeDriverExtension} from './src/webdriver/chrome_driver_extension';
14+
export {FirefoxDriverExtension} from './src/webdriver/firefox_driver_extension';
1415
export {IOsDriverExtension} from './src/webdriver/ios_driver_extension';
1516
export {Runner} from './src/runner';
1617
export {Options} from './src/common_options';
Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
declare var exportFunction;
22
declare var unsafeWindow;
33

4-
exportFunction(function() { (<any>self).port.emit('startProfiler'); }, unsafeWindow,
5-
{defineAs: "startProfiler"});
4+
exportFunction(function() {
5+
var curTime = unsafeWindow.performance.now();
6+
(<any>self).port.emit('startProfiler', curTime);
7+
}, unsafeWindow, {defineAs: "startProfiler"});
68

7-
exportFunction(function(filePath) { (<any>self).port.emit('stopAndRecord', filePath); },
8-
unsafeWindow, {defineAs: "stopAndRecord"});
9+
exportFunction(function() { (<any>self).port.emit('stopProfiler'); }, unsafeWindow,
10+
{defineAs: "stopProfiler"});
11+
12+
exportFunction(function(cb) {
13+
(<any>self).port.once('perfProfile', cb);
14+
(<any>self).port.emit('getProfile');
15+
}, unsafeWindow, {defineAs: "getProfile"});
916

1017
exportFunction(function() { (<any>self).port.emit('forceGC'); }, unsafeWindow,
1118
{defineAs: "forceGC"});
19+
20+
exportFunction(function(name) {
21+
var curTime = unsafeWindow.performance.now();
22+
(<any>self).port.emit('markStart', name, curTime);
23+
}, unsafeWindow, {defineAs: "markStart"});
24+
25+
exportFunction(function(name) {
26+
var curTime = unsafeWindow.performance.now();
27+
(<any>self).port.emit('markEnd', name, curTime);
28+
}, unsafeWindow, {defineAs: "markEnd"});
Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,66 @@
11
/// <reference path="../../../../angular2/typings/node/node.d.ts" />
22

3-
var file = require('sdk/io/file');
4-
var {Cc, Ci, Cu} = require("chrome");
3+
var {Cc, Ci, Cu} = require('chrome');
4+
var os = Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
5+
var ParserUtil = require('./parser_util');
56

67
class Profiler {
78
private _profiler;
8-
constructor() { this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); }
9+
private _markerEvents: List<any>;
10+
private _profilerStartTime: number;
911

10-
start(entries, interval, features) {
12+
constructor() { this._profiler = Cc['@mozilla.org/tools/profiler;1'].getService(Ci.nsIProfiler); }
13+
14+
start(entries, interval, features, timeStarted) {
1115
this._profiler.StartProfiler(entries, interval, features, features.length);
16+
this._profilerStartTime = timeStarted;
17+
this._markerEvents = [];
1218
}
1319

1420
stop() { this._profiler.StopProfiler(); }
15-
getProfileData() { return this._profiler.getProfileData(); }
16-
}
1721

22+
getProfilePerfEvents() {
23+
var profileData = this._profiler.getProfileData();
24+
var perfEvents = ParserUtil.convertPerfProfileToEvents(profileData);
25+
perfEvents = this._mergeMarkerEvents(perfEvents);
26+
perfEvents.sort(function(event1, event2) { return event1.ts - event2.ts; }); // Sort by ts
27+
return perfEvents;
28+
}
29+
30+
_mergeMarkerEvents(perfEvents: List<any>): List<any> {
31+
this._markerEvents.forEach(function(markerEvent) { perfEvents.push(markerEvent); });
32+
return perfEvents;
33+
}
34+
35+
addStartEvent(name: string, timeStarted: number) {
36+
this._markerEvents.push({ph: 'b', ts: timeStarted - this._profilerStartTime, name: name});
37+
}
1838

19-
function saveToFile(savePath: string, body: string) {
20-
var textWriter = file.open(savePath, 'w');
21-
textWriter.write(body);
22-
textWriter.close();
39+
addEndEvent(name: string, timeEnded: number) {
40+
this._markerEvents.push({ph: 'e', ts: timeEnded - this._profilerStartTime, name: name});
41+
}
2342
}
2443

2544
function forceGC() {
2645
Cu.forceGC();
27-
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
28-
os.notifyObservers(null, "child-gc-request", null);
29-
}
30-
31-
var profiler = new Profiler();
32-
function startProfiler() {
33-
profiler.start(/* = profiler memory */ 10000000, 1, ['leaf', 'js', "stackwalk", 'gc']);
46+
os.notifyObservers(null, 'child-gc-request', null);
3447
};
35-
function stopAndRecord(filePath) {
36-
var profileData = profiler.getProfileData();
37-
profiler.stop();
38-
saveToFile(filePath, JSON.stringify(profileData, null, 2));
39-
};
40-
4148

42-
var mod = require("sdk/page-mod");
43-
var data = require("sdk/self").data;
49+
var mod = require('sdk/page-mod');
50+
var data = require('sdk/self').data;
51+
var profiler = new Profiler();
4452
mod.PageMod({
4553
include: ['*'],
46-
contentScriptFile: data.url("installed_script.js"),
54+
contentScriptFile: data.url('installed_script.js'),
4755
onAttach: worker => {
48-
worker.port.on('startProfiler', () => startProfiler());
49-
worker.port.on('stopAndRecord', filePath => stopAndRecord(filePath));
50-
worker.port.on('forceGC', () => forceGC());
56+
worker.port.on('startProfiler',
57+
(timeStarted) => profiler.start(/* = profiler memory */ 1000000, 1,
58+
['leaf', 'js', 'stackwalk', 'gc'], timeStarted));
59+
worker.port.on('stopProfiler', () => profiler.stop());
60+
worker.port.on('getProfile',
61+
() => worker.port.emit('perfProfile', profiler.getProfilePerfEvents()));
62+
worker.port.on('forceGC', forceGC);
63+
worker.port.on('markStart', (name, timeStarted) => profiler.addStartEvent(name, timeStarted));
64+
worker.port.on('markEnd', (name, timeEnded) => profiler.addEndEvent(name, timeEnded));
5165
}
5266
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
library benchpress.src.firefox_extension.lib.parser_util;
2+
//no dart implementation
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/// <reference path="../../../../angular2/typings/node/node.d.ts" />
2+
3+
/**
4+
* @param {Object} perfProfile The perf profile JSON object.
5+
* @return {Array<Object>} An array of recognized events that are captured
6+
* within the perf profile.
7+
*/
8+
export function convertPerfProfileToEvents(perfProfile: any): List<any> {
9+
var inProgressEvents = new Map(); // map from event name to start time
10+
var finishedEvents = []; // Array<Event> finished events
11+
var addFinishedEvent = function(eventName, startTime, endTime) {
12+
var categorizedEventName = categorizeEvent(eventName);
13+
var args = undefined;
14+
if (categorizedEventName == 'gc') {
15+
// TODO: We cannot measure heap size at the moment
16+
args = {usedHeapSize: 0};
17+
}
18+
if (startTime == endTime) {
19+
// Finished instantly
20+
finishedEvents.push({ph: 'X', ts: startTime, name: categorizedEventName, args: args});
21+
} else {
22+
// Has duration
23+
finishedEvents.push({ph: 'B', ts: startTime, name: categorizedEventName, args: args});
24+
finishedEvents.push({ph: 'E', ts: endTime, name: categorizedEventName, args: args});
25+
}
26+
};
27+
28+
var samples = perfProfile.threads[0].samples;
29+
// In perf profile, firefox samples all the frames in set time intervals. Here
30+
// we go through all the samples and construct the start and end time for each
31+
// event.
32+
for (var i = 0; i < samples.length; ++i) {
33+
var sample = samples[i];
34+
var sampleTime = sample.time;
35+
36+
// Add all the frames into a set so it's easier/faster to find the set
37+
// differences
38+
var sampleFrames = new Set();
39+
sample.frames.forEach(function(frame) { sampleFrames.add(frame.location); });
40+
41+
// If an event is in the inProgressEvents map, but not in the current sample,
42+
// then it must have just finished. We add this event to the finishedEvents
43+
// array and remove it from the inProgressEvents map.
44+
var previousSampleTime = (i == 0 ? /* not used */ -1 : samples[i - 1].time);
45+
inProgressEvents.forEach(function(startTime, eventName) {
46+
if (!(sampleFrames.has(eventName))) {
47+
addFinishedEvent(eventName, startTime, previousSampleTime);
48+
inProgressEvents.delete(eventName);
49+
}
50+
});
51+
52+
// If an event is in the current sample, but not in the inProgressEvents map,
53+
// then it must have just started. We add this event to the inProgressEvents
54+
// map.
55+
sampleFrames.forEach(function(eventName) {
56+
if (!(inProgressEvents.has(eventName))) {
57+
inProgressEvents.set(eventName, sampleTime);
58+
}
59+
});
60+
}
61+
62+
// If anything is still in progress, we need to included it as a finished event
63+
// since recording ended.
64+
var lastSampleTime = samples[samples.length - 1].time;
65+
inProgressEvents.forEach(function(startTime, eventName) {
66+
addFinishedEvent(eventName, startTime, lastSampleTime);
67+
});
68+
69+
// Remove all the unknown categories.
70+
return finishedEvents.filter(function(event) { return event.name != 'unknown'; });
71+
}
72+
73+
// TODO: this is most likely not exhaustive.
74+
export function categorizeEvent(eventName: string): string {
75+
if (eventName.indexOf('PresShell::Paint') > -1) {
76+
return 'render';
77+
} else if (eventName.indexOf('FirefoxDriver.prototype.executeScript') > -1) {
78+
return 'script';
79+
} else if (eventName.indexOf('forceGC') > -1) {
80+
return 'gc';
81+
} else {
82+
return 'unknown';
83+
}
84+
}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"version": "0.0.1",
3-
"main": "lib/main.js",
4-
"name": "ffperf-addon"
5-
}
1+
{ "version" : "0.0.1", "main" : "lib/main.js", "name" : "ffperf-addon" }

modules/benchpress/src/runner.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {Validator} from './validator';
1212
import {PerflogMetric} from './metric/perflog_metric';
1313
import {MultiMetric} from './metric/multi_metric';
1414
import {ChromeDriverExtension} from './webdriver/chrome_driver_extension';
15+
import {FirefoxDriverExtension} from './webdriver/firefox_driver_extension';
1516
import {IOsDriverExtension} from './webdriver/ios_driver_extension';
1617
import {WebDriverExtension} from './web_driver_extension';
1718
import {SampleDescription} from './sample_description';
@@ -62,6 +63,7 @@ var _DEFAULT_BINDINGS = [
6263
RegressionSlopeValidator.BINDINGS,
6364
SizeValidator.BINDINGS,
6465
ChromeDriverExtension.BINDINGS,
66+
FirefoxDriverExtension.BINDINGS,
6567
IOsDriverExtension.BINDINGS,
6668
PerflogMetric.BINDINGS,
6769
SampleDescription.BINDINGS,
@@ -70,7 +72,7 @@ var _DEFAULT_BINDINGS = [
7072

7173
Reporter.bindTo(MultiReporter),
7274
Validator.bindTo(RegressionSlopeValidator),
73-
WebDriverExtension.bindTo([ChromeDriverExtension, IOsDriverExtension]),
75+
WebDriverExtension.bindTo([ChromeDriverExtension, FirefoxDriverExtension, IOsDriverExtension]),
7476
Metric.bindTo(MultiMetric),
7577

7678
bind(Options.CAPABILITIES)

modules/benchpress/src/web_driver_adapter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class WebDriverAdapter {
1616

1717
waitFor(callback: Function): Promise<any> { throw new BaseException('NYI'); }
1818
executeScript(script: string): Promise<any> { throw new BaseException('NYI'); }
19+
executeAsyncScript(script: string): Promise<any> { throw new BaseException('NYI'); }
1920
capabilities(): Promise<Map<string, any>> { throw new BaseException('NYI'); }
2021
logs(type: string): Promise<List<any>> { throw new BaseException('NYI'); }
2122
}

modules/benchpress/src/webdriver/async_webdriver_adapter.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class AsyncWebDriverAdapter extends WebDriverAdapter {
1616
return _driver.execute(script, const []);
1717
}
1818

19+
Future executeAsyncScript(String script) {
20+
return _driver.executeAsync(script, const []);
21+
}
22+
1923
Future<Map> capabilities() {
2024
return new Future.value(_driver.capabilities);
2125
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {bind, Binding} from 'angular2/di';
2+
import {isPresent, StringWrapper} from 'angular2/src/facade/lang';
3+
import {WebDriverExtension, PerfLogFeatures} from '../web_driver_extension';
4+
import {WebDriverAdapter} from '../web_driver_adapter';
5+
import {Promise} from 'angular2/src/facade/async';
6+
7+
export class FirefoxDriverExtension extends WebDriverExtension {
8+
static get BINDINGS(): List<Binding> { return _BINDINGS; }
9+
10+
private _profilerStarted: boolean;
11+
12+
constructor(private _driver: WebDriverAdapter) {
13+
super();
14+
this._profilerStarted = false;
15+
}
16+
17+
gc() { return this._driver.executeScript('window.forceGC()'); }
18+
19+
timeBegin(name: string): Promise<any> {
20+
if (!this._profilerStarted) {
21+
this._profilerStarted = true;
22+
this._driver.executeScript('window.startProfiler();');
23+
}
24+
return this._driver.executeScript('window.markStart("' + name + '");');
25+
}
26+
27+
timeEnd(name: string, restartName: string = null): Promise<any> {
28+
var script = 'window.markEnd("' + name + '");';
29+
if (isPresent(restartName)) {
30+
script += 'window.markStart("' + restartName + '");';
31+
}
32+
return this._driver.executeScript(script);
33+
}
34+
35+
readPerfLog(): Promise<any> {
36+
return this._driver.executeAsyncScript('var cb = arguments[0]; window.getProfile(cb);');
37+
}
38+
39+
perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true, gc: true}); }
40+
41+
supports(capabilities: StringMap<string, any>): boolean {
42+
return StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'firefox');
43+
}
44+
}
45+
46+
var _BINDINGS = [
47+
bind(FirefoxDriverExtension)
48+
.toFactory((driver) => new FirefoxDriverExtension(driver), [WebDriverAdapter])
49+
];

0 commit comments

Comments
 (0)