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:
parent
c4114c2f66
commit
838d4bbf6c
@ -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) {
|
||||||
|
@ -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,
|
||||||
|
@ -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};
|
||||||
|
@ -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 = [
|
||||||
[
|
[
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user