parent
8968b206b4
commit
989dea7083
|
@ -1,2 +0,0 @@
|
|||
*.xpi
|
||||
addon-sdk*
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
declare var exportFunction: any;
|
||||
declare var unsafeWindow: any;
|
||||
|
||||
exportFunction(function() {
|
||||
const curTime = unsafeWindow.performance.now();
|
||||
(<any>self).port.emit('startProfiler', curTime);
|
||||
}, unsafeWindow, {defineAs: 'startProfiler'});
|
||||
|
||||
exportFunction(function() {
|
||||
(<any>self).port.emit('stopProfiler');
|
||||
}, unsafeWindow, {defineAs: 'stopProfiler'});
|
||||
|
||||
exportFunction(function(cb: Function) {
|
||||
(<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: string) {
|
||||
const curTime = unsafeWindow.performance.now();
|
||||
(<any>self).port.emit('markStart', name, curTime);
|
||||
}, unsafeWindow, {defineAs: 'markStart'});
|
||||
|
||||
exportFunction(function(name: string) {
|
||||
const curTime = unsafeWindow.performance.now();
|
||||
(<any>self).port.emit('markEnd', name, curTime);
|
||||
}, unsafeWindow, {defineAs: 'markEnd'});
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
const {Cc, Ci, Cu} = require('chrome');
|
||||
const os = Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
|
||||
const ParserUtil = require('./parser_util');
|
||||
|
||||
class Profiler {
|
||||
private _profiler: any;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private _markerEvents !: any[];
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private _profilerStartTime !: number;
|
||||
|
||||
constructor() { this._profiler = Cc['@mozilla.org/tools/profiler;1'].getService(Ci.nsIProfiler); }
|
||||
|
||||
start(entries: any, interval: any, features: any, timeStarted: any) {
|
||||
this._profiler.StartProfiler(entries, interval, features, features.length);
|
||||
this._profilerStartTime = timeStarted;
|
||||
this._markerEvents = [];
|
||||
}
|
||||
|
||||
stop() { this._profiler.StopProfiler(); }
|
||||
|
||||
getProfilePerfEvents() {
|
||||
const profileData = this._profiler.getProfileData();
|
||||
let perfEvents = ParserUtil.convertPerfProfileToEvents(profileData);
|
||||
perfEvents = this._mergeMarkerEvents(perfEvents);
|
||||
perfEvents.sort(function(event1: any, event2: any) {
|
||||
return event1.ts - event2.ts;
|
||||
}); // Sort by ts
|
||||
return perfEvents;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private _mergeMarkerEvents(perfEvents: any[]): 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();
|
||||
os.notifyObservers(null, 'child-gc-request', null);
|
||||
}
|
||||
|
||||
const mod = require('sdk/page-mod');
|
||||
const data = require('sdk/self').data;
|
||||
const profiler = new Profiler();
|
||||
mod.PageMod({
|
||||
include: ['*'],
|
||||
contentScriptFile: data.url('installed_script.js'),
|
||||
onAttach: (worker: any) => {
|
||||
worker.port.on(
|
||||
'startProfiler',
|
||||
(timeStarted: any) => profiler.start(
|
||||
/* = profiler memory */ 3000000, 0.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: string, timeStarted: any) => profiler.addStartEvent(name, timeStarted));
|
||||
worker.port.on(
|
||||
'markEnd', (name: string, timeEnded: any) => profiler.addEndEvent(name, timeEnded));
|
||||
}
|
||||
});
|
|
@ -1,92 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Object} perfProfile The perf profile JSON object.
|
||||
* @return {Object[]} An array of recognized events that are captured
|
||||
* within the perf profile.
|
||||
*/
|
||||
export function convertPerfProfileToEvents(perfProfile: any): any[] {
|
||||
const inProgressEvents = new Map(); // map from event name to start time
|
||||
const finishedEvents: {[key: string]: any}[] = []; // Event[] finished events
|
||||
const addFinishedEvent = function(eventName: string, startTime: number, endTime: number) {
|
||||
const categorizedEventName = categorizeEvent(eventName);
|
||||
let args: {[key: string]: any}|undefined = 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});
|
||||
}
|
||||
};
|
||||
|
||||
const 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 (let i = 0; i < samples.length; ++i) {
|
||||
const sample = samples[i];
|
||||
const sampleTime = sample.time;
|
||||
|
||||
// Add all the frames into a set so it's easier/faster to find the set
|
||||
// differences
|
||||
const sampleFrames = new Set();
|
||||
sample.frames.forEach(function(frame: {[key: string]: any}) {
|
||||
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.
|
||||
const 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.
|
||||
const 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,51 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
const q = require('q');
|
||||
const FirefoxProfile = require('firefox-profile');
|
||||
const jpm = require('jpm/lib/xpi');
|
||||
const pathUtil = require('path');
|
||||
|
||||
const PERF_ADDON_PACKAGE_JSON_DIR = '..';
|
||||
|
||||
exports.getAbsolutePath = function(path: string) {
|
||||
const normalizedPath = pathUtil.normalize(path);
|
||||
if (pathUtil.resolve(normalizedPath) == normalizedPath) {
|
||||
// Already absolute path
|
||||
return normalizedPath;
|
||||
} else {
|
||||
return pathUtil.join(__dirname, normalizedPath);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getFirefoxProfile = function(extensionPath: string) {
|
||||
const deferred = q.defer();
|
||||
|
||||
const firefoxProfile = new FirefoxProfile();
|
||||
firefoxProfile.addExtensions([extensionPath], () => {
|
||||
firefoxProfile.encoded((err: any, encodedProfile: string) => {
|
||||
const multiCapabilities = [{browserName: 'firefox', firefox_profile: encodedProfile}];
|
||||
deferred.resolve(multiCapabilities);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
exports.getFirefoxProfileWithExtension = function() {
|
||||
const absPackageJsonDir = pathUtil.join(__dirname, PERF_ADDON_PACKAGE_JSON_DIR);
|
||||
const packageJson = require(pathUtil.join(absPackageJsonDir, 'package.json'));
|
||||
|
||||
const savedCwd = process.cwd();
|
||||
process.chdir(absPackageJsonDir);
|
||||
|
||||
return jpm(packageJson).then((xpiPath: string) => {
|
||||
process.chdir(savedCwd);
|
||||
return exports.getFirefoxProfile(xpiPath);
|
||||
});
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
{ "version" : "0.0.1", "main" : "lib/main.js", "name" : "ffperf-addon" }
|
|
@ -1,21 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
require('core-js');
|
||||
require('reflect-metadata');
|
||||
const testHelper = require('../../src/firefox_extension/lib/test_helper.js');
|
||||
|
||||
exports.config = {
|
||||
specs: ['spec.js', 'sample_benchmark.js'],
|
||||
|
||||
framework: 'jasmine2',
|
||||
|
||||
jasmineNodeOpts: {showColors: true, defaultTimeoutInterval: 1200000},
|
||||
|
||||
getMultiCapabilities: function() { return testHelper.getFirefoxProfileWithExtension(); }
|
||||
};
|
|
@ -1,100 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {convertPerfProfileToEvents} from '../../src/firefox_extension/lib/parser_util';
|
||||
|
||||
function assertEventsEqual(actualEvents: any[], expectedEvents: any[]) {
|
||||
expect(actualEvents.length == expectedEvents.length);
|
||||
for (let i = 0; i < actualEvents.length; ++i) {
|
||||
const actualEvent = actualEvents[i];
|
||||
const expectedEvent = expectedEvents[i];
|
||||
for (const key in actualEvent) {
|
||||
expect(actualEvent[key]).toEqual(expectedEvent[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
describe('convertPerfProfileToEvents', function() {
|
||||
it('should convert single instantaneous event', function() {
|
||||
const profileData = {
|
||||
threads: [
|
||||
{samples: [{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}]}
|
||||
]
|
||||
};
|
||||
const perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]);
|
||||
});
|
||||
|
||||
it('should convert single non-instantaneous event', function() {
|
||||
const profileData = {
|
||||
threads: [{
|
||||
samples: [
|
||||
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 2, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 100, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}
|
||||
]
|
||||
}]
|
||||
};
|
||||
const perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(
|
||||
perfEvents, [{ph: 'B', ts: 1, name: 'script'}, {ph: 'E', ts: 100, name: 'script'}]);
|
||||
});
|
||||
|
||||
it('should convert multiple instantaneous events', function() {
|
||||
const profileData = {
|
||||
threads: [{
|
||||
samples: [
|
||||
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 2, frames: [{location: 'PresShell::Paint'}]}
|
||||
]
|
||||
}]
|
||||
};
|
||||
const perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(
|
||||
perfEvents, [{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'}]);
|
||||
});
|
||||
|
||||
it('should convert multiple mixed events', function() {
|
||||
const profileData = {
|
||||
threads: [{
|
||||
samples: [
|
||||
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 2, frames: [{location: 'PresShell::Paint'}]},
|
||||
{time: 5, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 10, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}
|
||||
]
|
||||
}]
|
||||
};
|
||||
const perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(perfEvents, [
|
||||
{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'},
|
||||
{ph: 'B', ts: 5, name: 'script'}, {ph: 'E', ts: 10, name: 'script'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add args to gc events', function() {
|
||||
const profileData = {threads: [{samples: [{time: 1, frames: [{location: 'forceGC'}]}]}]};
|
||||
const perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'gc', args: {usedHeapSize: 0}}]);
|
||||
});
|
||||
|
||||
it('should skip unknown events', function() {
|
||||
const profileData = {
|
||||
threads: [{
|
||||
samples: [
|
||||
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
|
||||
{time: 2, frames: [{location: 'foo'}]}
|
||||
]
|
||||
}]
|
||||
};
|
||||
const perfEvents = convertPerfProfileToEvents(profileData);
|
||||
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {$, browser} from 'protractor';
|
||||
|
||||
const benchpress = require('../../index.js');
|
||||
|
||||
// TODO: this test is currnetly failing. it seems that it didn't run on the ci for a while
|
||||
xdescribe('deep tree baseline', function() {
|
||||
const runner = new benchpress.Runner([
|
||||
// use protractor as Webdriver client
|
||||
benchpress.SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS,
|
||||
// use RegressionSlopeValidator to validate samples
|
||||
benchpress.Validator.bind(benchpress.RegressionSlopeValidator),
|
||||
// use 10 samples to calculate slope regression
|
||||
benchpress.bind(benchpress.RegressionSlopeValidator.SAMPLE_SIZE).toValue(20),
|
||||
// use the script metric to calculate slope regression
|
||||
benchpress.bind(benchpress.RegressionSlopeValidator.METRIC).toValue('scriptTime'),
|
||||
benchpress.bind(benchpress.Options.FORCE_GC).toValue(true)
|
||||
]);
|
||||
|
||||
|
||||
it('should be fast!', function(done) {
|
||||
browser.ignoreSynchronization = true;
|
||||
browser.get('http://localhost:8001/playground/src/benchpress/');
|
||||
|
||||
/*
|
||||
* Tell benchpress to click the buttons to destroy and re-create the tree for each sample.
|
||||
* Benchpress will log the collected metrics after each sample is collected, and will stop
|
||||
* sampling as soon as the calculated regression slope for last 20 samples is stable.
|
||||
*/
|
||||
runner
|
||||
.sample({
|
||||
id: 'baseline',
|
||||
execute: function() { $('button').click(); },
|
||||
providers: [benchpress.bind(benchpress.Options.SAMPLE_DESCRIPTION).toValue({depth: 9})]
|
||||
})
|
||||
.then(done, done.fail);
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/* tslint:disable:no-console */
|
||||
import {browser} from 'protractor';
|
||||
|
||||
const assertEventsContainsName = function(events: any[], eventName: string) {
|
||||
let found = false;
|
||||
for (let i = 0; i < events.length; ++i) {
|
||||
if (events[i].name == eventName) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(found).toBeTruthy();
|
||||
};
|
||||
|
||||
// TODO: this test is currnetly failing. it seems that it didn't run on the ci for a while
|
||||
xdescribe('firefox extension', function() {
|
||||
const TEST_URL = 'http://localhost:8001/playground/src/hello_world/index.html';
|
||||
|
||||
it('should measure performance', function() {
|
||||
browser.sleep(3000); // wait for extension to load
|
||||
|
||||
browser.driver.get(TEST_URL);
|
||||
|
||||
browser.executeScript('window.startProfiler()').then(function() {
|
||||
console.log('started measuring perf');
|
||||
});
|
||||
|
||||
browser.executeAsyncScript('setTimeout(arguments[0], 1000);');
|
||||
browser.executeScript('window.forceGC()');
|
||||
|
||||
browser.executeAsyncScript('var cb = arguments[0]; window.getProfile(cb);')
|
||||
.then(function(profile: any) {
|
||||
assertEventsContainsName(profile, 'gc');
|
||||
assertEventsContainsName(profile, 'script');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue