fix(benchpress): support measuring scriptTime and other metrics of page reload.

E.g. for benchmarks that measure page start time
This commit is contained in:
Tobias Bosch 2016-09-15 08:51:55 -07:00 committed by Alex Eagle
parent c4114c2f66
commit 838d4bbf6c
6 changed files with 124 additions and 87 deletions

View File

@ -219,6 +219,24 @@ export class PerflogMetric extends Metric {
var markStartEvent: PerfLogEvent = null; var markStartEvent: PerfLogEvent = null;
var markEndEvent: PerfLogEvent = null; var markEndEvent: PerfLogEvent = null;
events.forEach((event) => {
var ph = event['ph'];
var name = event['name'];
if (ph === 'b' && name === markName) {
markStartEvent = event;
} else if (ph === 'I' && name === 'navigationStart') {
// if a benchmark measures reload of a page, use the last
// navigationStart as begin event
markStartEvent = event;
} else if (ph === 'e' && name === markName) {
markEndEvent = event;
}
});
if (!markStartEvent || !markEndEvent) {
// not all events have been received, no further processing for now
return null;
}
var gcTimeInScript = 0; var gcTimeInScript = 0;
var renderTimeInScript = 0; var renderTimeInScript = 0;
@ -229,6 +247,8 @@ export class PerflogMetric extends Metric {
var intervalStarts: {[key: string]: PerfLogEvent} = {}; var intervalStarts: {[key: string]: PerfLogEvent} = {};
var intervalStartCount: {[key: string]: number} = {}; var intervalStartCount: {[key: string]: number} = {};
var inMeasureRange = false;
events.forEach((event) => { events.forEach((event) => {
var ph = event['ph']; var ph = event['ph'];
var name = event['name']; var name = event['name'];
@ -238,102 +258,85 @@ export class PerflogMetric extends Metric {
name = microIterationsMatch[1]; name = microIterationsMatch[1];
microIterations = parseInt(microIterationsMatch[2], 10); microIterations = parseInt(microIterationsMatch[2], 10);
} }
if (event === markStartEvent) {
if (ph === 'b' && name === markName) { inMeasureRange = true;
markStartEvent = event; } else if (event === markEndEvent) {
} else if (ph === 'e' && name === markName) { inMeasureRange = false;
markEndEvent = event; }
if (!inMeasureRange || event['pid'] !== markStartEvent['pid']) {
return;
} }
let isInstant = ph === 'I' || ph === 'i';
if (this._requestCount && name === 'sendRequest') { if (this._requestCount && name === 'sendRequest') {
result['requestCount'] += 1; result['requestCount'] += 1;
} else if (this._receivedData && name === 'receivedData' && isInstant) { } else if (this._receivedData && name === 'receivedData' && ph === 'I') {
result['receivedData'] += event['args']['encodedDataLength']; result['receivedData'] += event['args']['encodedDataLength'];
} else if (name === 'navigationStart') { }
// We count data + requests since the last navigationStart if (ph === 'b' && name === _MARK_NAME_FRAME_CAPUTRE) {
// (there might be chrome extensions loaded by selenium before our page, so there if (frameCaptureStartEvent) {
// will likely be more than one navigationStart). throw new Error('can capture frames only once per benchmark run');
if (this._receivedData) {
result['receivedData'] = 0;
} }
if (this._requestCount) { if (!this._captureFrames) {
result['requestCount'] = 0; throw new Error(
'found start event for frame capture, but frame capture was not requested in benchpress');
}
frameCaptureStartEvent = event;
} else if (ph === 'e' && name === _MARK_NAME_FRAME_CAPUTRE) {
if (!frameCaptureStartEvent) {
throw new Error('missing start event for frame capture');
}
frameCaptureEndEvent = event;
}
if (ph === 'I' && frameCaptureStartEvent && !frameCaptureEndEvent && name === 'frame') {
frameTimestamps.push(event['ts']);
if (frameTimestamps.length >= 2) {
frameTimes.push(
frameTimestamps[frameTimestamps.length - 1] -
frameTimestamps[frameTimestamps.length - 2]);
} }
} }
if (markStartEvent && !markEndEvent && event['pid'] === markStartEvent['pid']) {
if (ph === 'b' && name === _MARK_NAME_FRAME_CAPUTRE) {
if (frameCaptureStartEvent) {
throw new Error('can capture frames only once per benchmark run');
}
if (!this._captureFrames) {
throw new Error(
'found start event for frame capture, but frame capture was not requested in benchpress');
}
frameCaptureStartEvent = event;
} else if (ph === 'e' && name === _MARK_NAME_FRAME_CAPUTRE) {
if (!frameCaptureStartEvent) {
throw new Error('missing start event for frame capture');
}
frameCaptureEndEvent = event;
}
if (isInstant) { if (ph === 'B' || ph === 'b') {
if (frameCaptureStartEvent && !frameCaptureEndEvent && name === 'frame') { if (!intervalStarts[name]) {
frameTimestamps.push(event['ts']); intervalStartCount[name] = 1;
if (frameTimestamps.length >= 2) { intervalStarts[name] = event;
frameTimes.push( } else {
frameTimestamps[frameTimestamps.length - 1] - intervalStartCount[name]++;
frameTimestamps[frameTimestamps.length - 2]);
}
}
} }
} else if ((ph === 'E' || ph === 'e') && intervalStarts[name]) {
if (ph === 'B' || ph === 'b') { intervalStartCount[name]--;
if (!intervalStarts[name]) { if (intervalStartCount[name] === 0) {
intervalStartCount[name] = 1; var startEvent = intervalStarts[name];
intervalStarts[name] = event; var duration = (event['ts'] - startEvent['ts']);
} else { intervalStarts[name] = null;
intervalStartCount[name]++; if (name === 'gc') {
} result['gcTime'] += duration;
} else if ((ph === 'E' || ph === 'e') && intervalStarts[name]) { var amount =
intervalStartCount[name]--; (startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000;
if (intervalStartCount[name] === 0) { result['gcAmount'] += amount;
var startEvent = intervalStarts[name]; var majorGc = event['args']['majorGc'];
var duration = (event['ts'] - startEvent['ts']); if (majorGc && majorGc) {
intervalStarts[name] = null; result['majorGcTime'] += duration;
if (name === 'gc') {
result['gcTime'] += duration;
var amount =
(startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000;
result['gcAmount'] += amount;
var majorGc = event['args']['majorGc'];
if (majorGc && majorGc) {
result['majorGcTime'] += duration;
}
if (intervalStarts['script']) {
gcTimeInScript += duration;
}
} else if (name === 'render') {
result['renderTime'] += duration;
if (intervalStarts['script']) {
renderTimeInScript += duration;
}
} else if (name === 'script') {
result['scriptTime'] += duration;
} else if (this._microMetrics[name]) {
(<any>result)[name] += duration / microIterations;
} }
if (intervalStarts['script']) {
gcTimeInScript += duration;
}
} else if (name === 'render') {
result['renderTime'] += duration;
if (intervalStarts['script']) {
renderTimeInScript += duration;
}
} else if (name === 'script') {
result['scriptTime'] += duration;
} else if (this._microMetrics[name]) {
(<any>result)[name] += duration / microIterations;
} }
} }
} }
}); });
if (!markStartEvent || !markEndEvent) {
// not all events have been received, no further processing for now
return null;
}
if (markEndEvent && frameCaptureStartEvent && !frameCaptureEndEvent) { if (frameCaptureStartEvent && !frameCaptureEndEvent) {
throw new Error('missing end event for frame capture'); throw new Error('missing end event for frame capture');
} }
if (this._captureFrames && !frameCaptureStartEvent) { if (this._captureFrames && !frameCaptureStartEvent) {

View File

@ -15,7 +15,7 @@ export type PerfLogEvent = {
[key: string]: any [key: string]: any
} & { } & {
cat?: string, cat?: string,
ph?: 'X' | 'B' | 'E' | 'b' | 'e' | 'i' | 'I', ph?: 'X' | 'B' | 'E' | 'b' | 'e' | 'I',
ts?: number, ts?: number,
dur?: number, dur?: number,
name?: string, name?: string,

View File

@ -140,6 +140,8 @@ export class ChromeDriverExtension extends WebDriverExtension {
(!args || !args['data'] || (!args || !args['data'] ||
(args['data']['scriptName'] !== 'InjectedScript' && args['data']['scriptName'] !== ''))) { (args['data']['scriptName'] !== 'InjectedScript' && args['data']['scriptName'] !== ''))) {
return normalizeEvent(event, {'name': 'script'}); return normalizeEvent(event, {'name': 'script'});
} else if (this._isEvent(categories, name, ['devtools.timeline'], 'EvaluateScript')) {
return normalizeEvent(event, {'name': 'script'});
} else if (this._isEvent( } else if (this._isEvent(
categories, name, ['devtools.timeline', 'blink'], 'UpdateLayoutTree')) { categories, name, ['devtools.timeline', 'blink'], 'UpdateLayoutTree')) {
return normalizeEvent(event, {'name': 'render'}); return normalizeEvent(event, {'name': 'render'});
@ -187,6 +189,12 @@ function normalizeEvent(
ph = 'b'; ph = 'b';
} else if (ph === 'F') { } else if (ph === 'F') {
ph = 'e'; ph = 'e';
} else if (ph === 'R') {
// mark events from navigation timing
ph = 'I';
} else if (ph === 'i') {
// legacy support
ph = 'I';
} }
var result: {[key: string]: any} = var result: {[key: string]: any} =
{'pid': chromeEvent['pid'], 'ph': ph, 'cat': 'timeline', 'ts': chromeEvent['ts'] / 1000}; {'pid': chromeEvent['pid'], 'ph': ph, 'cat': 'timeline', 'ts': chromeEvent['ts'] / 1000};

View File

@ -171,6 +171,22 @@ export function main() {
}); });
})); }));
it('should mark and aggregate events since navigationStart',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var events = [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
eventFactory.end('script', 6), eventFactory.instant('navigationStart', 7),
eventFactory.start('script', 8), eventFactory.end('script', 9),
eventFactory.markEnd('benchpress0', 10)
]];
var metric = createMetric(events, null);
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(data['scriptTime']).toBe(1);
async.done();
});
}));
it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var events = [ var events = [
[ [

View File

@ -30,7 +30,7 @@ export class TraceEventFactory {
end(name: string, time: number, args: any = null) { return this.create('E', name, time, args); } end(name: string, time: number, args: any = null) { return this.create('E', name, time, args); }
instant(name: string, time: number, args: any = null) { instant(name: string, time: number, args: any = null) {
return this.create('i', name, time, args); return this.create('I', name, time, args);
} }
complete(name: string, time: number, duration: number, args: any = null) { complete(name: string, time: number, duration: number, args: any = null) {

View File

@ -123,6 +123,18 @@ export function main() {
}); });
})); }));
it('should report EvaluateScript events as "script"',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chromeTimelineV8Events.start('EvaluateScript', 0)])
.readPerfLog()
.then((events) => {
expect(events).toEqual([
normEvents.start('script', 0),
]);
async.done();
});
}));
it('should report minor gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should report minor gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([ createExtension([
chromeTimelineV8Events.start('MinorGC', 1000, {'usedHeapSizeBefore': 1000}), chromeTimelineV8Events.start('MinorGC', 1000, {'usedHeapSizeBefore': 1000}),
@ -192,8 +204,6 @@ export function main() {
}); });
})); }));
it('should ignore FunctionCalls from webdriver', it('should ignore FunctionCalls from webdriver',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chromeTimelineV8Events.start( createExtension([chromeTimelineV8Events.start(
@ -218,10 +228,10 @@ export function main() {
it('should report navigationStart', it('should report navigationStart',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
createExtension([chromeBlinkUserTimingEvents.start('navigationStart', 1234)]) createExtension([chromeBlinkUserTimingEvents.instant('navigationStart', 1234)])
.readPerfLog() .readPerfLog()
.then((events) => { .then((events) => {
expect(events).toEqual([normEvents.start('navigationStart', 1.234)]); expect(events).toEqual([normEvents.instant('navigationStart', 1.234)]);
async.done(); async.done();
}); });
})); }));
@ -290,7 +300,7 @@ export function main() {
.readPerfLog() .readPerfLog()
.then((events) => { .then((events) => {
expect(events).toEqual([ expect(events).toEqual([
normEvents.create('i', 'frame', 1.1), normEvents.instant('frame', 1.1),
]); ]);
async.done(); async.done();
}); });