Skip to content

Commit 7aa031b

Browse files
committed
feat(bench press): use chrome tracing protocol and initial iOS support
1 parent 8a3d905 commit 7aa031b

14 files changed

+733
-332
lines changed

modules/angular2/src/facade/collection.dart

+3
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ class ListWrapper {
138138
static List slice(List l, int from, int to) {
139139
return l.sublist(from, to);
140140
}
141+
static void sort(List l, compareFn(a,b)) {
142+
l.sort(compareFn);
143+
}
141144
}
142145

143146
bool isListLikeIterable(obj) => obj is Iterable;

modules/angular2/src/facade/collection.es6

+4
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ export class ListWrapper {
185185
static slice(l:List, from:int, to:int):List {
186186
return l.slice(from, to);
187187
}
188+
static sort(l:List, compareFn:Function) {
189+
l.sort(compareFn);
190+
return l;
191+
}
188192
}
189193

190194
export function isListLikeIterable(obj):boolean {

modules/benchpress/benchpress.js

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export { ConsoleReporter } from './src/reporter/console_reporter';
1010
export { SampleDescription } from './src/sample_description';
1111
export { PerflogMetric } from './src/metric/perflog_metric';
1212
export { ChromeDriverExtension } from './src/webdriver/chrome_driver_extension';
13+
export { IOsDriverExtension } from './src/webdriver/ios_driver_extension';
1314
export { Runner } from './src/runner';
1415
export { Options } from './src/sample_options';
1516
export { MeasureValues } from './src/measure_values';

modules/benchpress/src/metric/perflog_metric.js

+50-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { PromiseWrapper, Promise } from 'angular2/src/facade/async';
22
import { isPresent, isBlank, int, BaseException, StringWrapper } from 'angular2/src/facade/lang';
3-
import { ListWrapper, StringMap } from 'angular2/src/facade/collection';
3+
import { ListWrapper, StringMap, StringMapWrapper } from 'angular2/src/facade/collection';
44
import { bind, OpaqueToken } from 'angular2/di';
55

66
import { WebDriverExtension } from '../web_driver_extension';
@@ -33,9 +33,9 @@ export class PerflogMetric extends Metric {
3333
'script': 'script execution time in ms',
3434
'render': 'render time in ms',
3535
'gcTime': 'gc time in ms',
36-
'gcAmount': 'gc amount in bytes',
36+
'gcAmount': 'gc amount in kbytes',
3737
'gcTimeInScript': 'gc time during script execution in ms',
38-
'gcAmountInScript': 'gc amount during script execution in bytes'
38+
'gcAmountInScript': 'gc amount during script execution in kbytes'
3939
};
4040
}
4141

@@ -50,12 +50,12 @@ export class PerflogMetric extends Metric {
5050
.then( (_) => this._readUntilEndMark(markName) );
5151
}
5252

53-
_readUntilEndMark(markName:string, loopCount:int = 0) {
53+
_readUntilEndMark(markName:string, loopCount:int = 0, startEvent = null) {
54+
if (loopCount > _MAX_RETRY_COUNT) {
55+
throw new BaseException(`Tried too often to get the ending mark: ${loopCount}`);
56+
}
5457
return this._driverExtension.readPerfLog().then( (events) => {
55-
this._remainingEvents = ListWrapper.concat(this._remainingEvents, events);
56-
if (loopCount > _MAX_RETRY_COUNT) {
57-
throw new BaseException(`Tried too often to get the ending mark: ${loopCount}`);
58-
}
58+
this._addEvents(events);
5959
var result = this._aggregateEvents(
6060
this._remainingEvents, markName
6161
);
@@ -72,6 +72,34 @@ export class PerflogMetric extends Metric {
7272
});
7373
}
7474

75+
_addEvents(events) {
76+
var needSort = false;
77+
ListWrapper.forEach(events, (event) => {
78+
if (StringWrapper.equals(event['ph'], 'X')) {
79+
needSort = true;
80+
var startEvent = {};
81+
var endEvent = {};
82+
StringMapWrapper.forEach(event, (value, prop) => {
83+
startEvent[prop] = value;
84+
endEvent[prop] = value;
85+
});
86+
startEvent['ph'] = 'B';
87+
endEvent['ph'] = 'E';
88+
endEvent['ts'] = startEvent['ts'] + startEvent['dur'];
89+
ListWrapper.push(this._remainingEvents, startEvent);
90+
ListWrapper.push(this._remainingEvents, endEvent);
91+
} else {
92+
ListWrapper.push(this._remainingEvents, event);
93+
}
94+
});
95+
if (needSort) {
96+
// Need to sort because of the ph==='X' events
97+
ListWrapper.sort(this._remainingEvents, (a,b) => {
98+
return a['ts'] - b['ts'];
99+
});
100+
}
101+
}
102+
75103
_aggregateEvents(events, markName) {
76104
var result = {
77105
'script': 0,
@@ -82,49 +110,40 @@ export class PerflogMetric extends Metric {
82110
'gcAmountInScript': 0
83111
};
84112

85-
var startMarkFound = false;
86-
var endMarkFound = false;
87-
if (isBlank(markName)) {
88-
startMarkFound = true;
89-
endMarkFound = true;
90-
}
113+
var markStartEvent = null;
114+
var markEndEvent = null;
91115

92116
var intervalStarts = {};
93117
events.forEach( (event) => {
94118
var ph = event['ph'];
95119
var name = event['name'];
96-
var ts = event['ts'];
97-
var args = event['args'];
98120
if (StringWrapper.equals(ph, 'b') && StringWrapper.equals(name, markName)) {
99-
startMarkFound = true;
121+
markStartEvent = event;
100122
} else if (StringWrapper.equals(ph, 'e') && StringWrapper.equals(name, markName)) {
101-
endMarkFound = true;
123+
markEndEvent = event;
102124
}
103-
if (startMarkFound && !endMarkFound) {
125+
if (isPresent(markStartEvent) && isBlank(markEndEvent) && event['pid'] === markStartEvent['pid']) {
104126
if (StringWrapper.equals(ph, 'B')) {
105-
intervalStarts[name] = ts;
127+
intervalStarts[name] = event;
106128
} else if (StringWrapper.equals(ph, 'E') && isPresent(intervalStarts[name])) {
107-
var diff = ts - intervalStarts[name];
129+
var startEvent = intervalStarts[name];
130+
var duration = event['ts'] - startEvent['ts'];
108131
intervalStarts[name] = null;
109132
if (StringWrapper.equals(name, 'gc')) {
110-
result['gcTime'] += diff;
111-
var gcAmount = 0;
112-
if (isPresent(args)) {
113-
gcAmount = args['amount'];
114-
}
115-
result['gcAmount'] += gcAmount;
133+
result['gcTime'] += duration;
134+
result['gcAmount'] += (startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000;
116135
if (isPresent(intervalStarts['script'])) {
117-
result['gcTimeInScript'] += diff;
118-
result['gcAmountInScript'] += gcAmount;
136+
result['gcTimeInScript'] += duration;
137+
result['gcAmountInScript'] += result['gcAmount'];
119138
}
120139
} else {
121-
result[name] += diff;
140+
result[name] += duration;
122141
}
123142
}
124143
}
125144
});
126145
result['script'] -= result['gcTimeInScript'];
127-
return startMarkFound && endMarkFound ? result : null;
146+
return isPresent(markStartEvent) && isPresent(markEndEvent) ? result : null;
128147
}
129148

130149
_markName(index) {

modules/benchpress/src/web_driver_extension.js

+6-8
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ export class WebDriverExtension {
1313
throw new BaseException('NYI');
1414
}
1515

16-
timeStamp(name:string, names:List<String>):Promise {
17-
throw new BaseException('NYI');
18-
}
19-
2016
timeBegin(name):Promise {
2117
throw new BaseException('NYI');
2218
}
@@ -27,10 +23,12 @@ export class WebDriverExtension {
2723

2824
/**
2925
* Format:
30-
* - name: event name, e.g. 'script', 'gc', ...
31-
* - ph: phase: 'B' (begin), 'E' (end), 'b' (nestable start), 'e' (nestable end)
32-
* - ts: timestamp, e.g. 12345
33-
* - args: arguments, e.g. {someArg: 1}
26+
* - cat: category of the event
27+
* - name: event name: 'script', 'gc', 'render', ...
28+
* - ph: phase: 'B' (begin), 'E' (end), 'b' (nestable start), 'e' (nestable end), 'X' (Complete event)
29+
* - ts: timestamp in ms, e.g. 12345
30+
* - pid: process id
31+
* - args: arguments, e.g. {heapSize: 1234}
3432
*
3533
* Based on [Chrome Trace Event Format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit)
3634
**/

modules/benchpress/src/webdriver/chrome_driver_extension.js

+68-92
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import { bind } from 'angular2/di';
2-
import { ListWrapper } from 'angular2/src/facade/collection';
2+
import { ListWrapper, StringMapWrapper } from 'angular2/src/facade/collection';
33
import {
4-
Json, isPresent, isBlank, RegExpWrapper, StringWrapper
4+
Json, isPresent, isBlank, RegExpWrapper, StringWrapper, BaseException, NumberWrapper
55
} from 'angular2/src/facade/lang';
66

77
import { WebDriverExtension } from '../web_driver_extension';
88
import { WebDriverAdapter } from '../web_driver_adapter';
99
import { Promise } from 'angular2/src/facade/async';
1010

1111

12-
var BEGIN_MARK_RE = RegExpWrapper.create('begin_(.*)');
13-
var END_MARK_RE = RegExpWrapper.create('end_(.*)');
14-
1512
export class ChromeDriverExtension extends WebDriverExtension {
1613
// TODO(tbosch): use static values when our transpiler supports them
1714
static get BINDINGS() { return _BINDINGS; }
@@ -28,15 +25,13 @@ export class ChromeDriverExtension extends WebDriverExtension {
2825
}
2926

3027
timeBegin(name:string):Promise {
31-
// Note: Can't use console.time / console.timeEnd as it does not show up in the perf log!
32-
return this._driver.executeScript(`console.timeStamp('begin_${name}');`);
28+
return this._driver.executeScript(`console.time('${name}');`);
3329
}
3430

3531
timeEnd(name:string, restartName:string = null):Promise {
36-
// Note: Can't use console.time / console.timeEnd as it does not show up in the perf log!
37-
var script = `console.timeStamp('end_${name}');`;
32+
var script = `console.timeEnd('${name}');`;
3833
if (isPresent(restartName)) {
39-
script += `console.timeStamp('begin_${restartName}');`
34+
script += `console.time('${restartName}');`
4035
}
4136
return this._driver.executeScript(script);
4237
}
@@ -47,100 +42,81 @@ export class ChromeDriverExtension extends WebDriverExtension {
4742
return this._driver.executeScript('1+1')
4843
.then( (_) => this._driver.logs('performance') )
4944
.then( (entries) => {
50-
var records = [];
45+
var events = [];
5146
ListWrapper.forEach(entries, function(entry) {
5247
var message = Json.parse(entry['message'])['message'];
53-
if (StringWrapper.equals(message['method'], 'Timeline.eventRecorded')) {
54-
ListWrapper.push(records, message['params']['record']);
48+
if (StringWrapper.equals(message['method'], 'Tracing.dataCollected')) {
49+
ListWrapper.push(events, message['params']);
50+
}
51+
if (StringWrapper.equals(message['method'], 'Tracing.bufferUsage')) {
52+
throw new BaseException('The DevTools trace buffer filled during the test!');
5553
}
5654
});
57-
return this._convertPerfRecordsToEvents(records);
55+
return this._convertPerfRecordsToEvents(events);
5856
});
5957
}
6058

61-
_convertPerfRecordsToEvents(records, events = null) {
62-
if (isBlank(events)) {
63-
events = [];
59+
_convertPerfRecordsToEvents(chromeEvents, normalizedEvents = null) {
60+
if (isBlank(normalizedEvents)) {
61+
normalizedEvents = [];
6462
}
65-
records.forEach( (record) => {
66-
var endEvent = null;
67-
var type = record['type'];
68-
var data = record['data'];
69-
var startTime = record['startTime'];
70-
var endTime = record['endTime'];
71-
72-
if (StringWrapper.equals(type, 'FunctionCall') &&
73-
(isBlank(data) || !StringWrapper.equals(data['scriptName'], 'InjectedScript'))) {
74-
ListWrapper.push(events, {
75-
'name': 'script',
76-
'ts': startTime,
77-
'ph': 'B'
78-
});
79-
endEvent = {
80-
'name': 'script',
81-
'ts': endTime,
82-
'ph': 'E',
83-
'args': null
84-
}
85-
} else if (StringWrapper.equals(type, 'TimeStamp')) {
86-
var name = data['message'];
87-
var ph;
88-
var match = RegExpWrapper.firstMatch(BEGIN_MARK_RE, name);
89-
if (isPresent(match)) {
90-
ph = 'b';
91-
} else {
92-
match = RegExpWrapper.firstMatch(END_MARK_RE, name);
93-
if (isPresent(match)) {
94-
ph = 'e';
95-
}
96-
}
97-
if (isPresent(ph)) {
98-
ListWrapper.push(events, {
99-
'name': match[1],
100-
'ph': ph
101-
});
102-
}
103-
} else if (StringWrapper.equals(type, 'RecalculateStyles') ||
104-
StringWrapper.equals(type, 'Layout') ||
105-
StringWrapper.equals(type, 'UpdateLayerTree') ||
106-
StringWrapper.equals(type, 'Paint') ||
107-
StringWrapper.equals(type, 'Rasterize') ||
108-
StringWrapper.equals(type, 'CompositeLayers')) {
109-
ListWrapper.push(events, {
110-
'name': 'render',
111-
'ts': startTime,
112-
'ph': 'B'
113-
});
114-
endEvent = {
115-
'name': 'render',
116-
'ts': endTime,
117-
'ph': 'E',
118-
'args': null
63+
chromeEvents.forEach( (event) => {
64+
var cat = event['cat'];
65+
var name = event['name'];
66+
var args = event['args'];
67+
if (StringWrapper.equals(cat, 'disabled-by-default-devtools.timeline')) {
68+
if (StringWrapper.equals(name, 'FunctionCall') &&
69+
(isBlank(args) || isBlank(args['data']) || !StringWrapper.equals(args['data']['scriptName'], 'InjectedScript'))) {
70+
ListWrapper.push(normalizedEvents, normalizeEvent(event, {
71+
'name': 'script'
72+
}));
73+
} else if (StringWrapper.equals(name, 'RecalculateStyles') ||
74+
StringWrapper.equals(name, 'Layout') ||
75+
StringWrapper.equals(name, 'UpdateLayerTree') ||
76+
StringWrapper.equals(name, 'Paint') ||
77+
StringWrapper.equals(name, 'Rasterize') ||
78+
StringWrapper.equals(name, 'CompositeLayers')) {
79+
ListWrapper.push(normalizedEvents, normalizeEvent(event, {
80+
'name': 'render'
81+
}));
82+
} else if (StringWrapper.equals(name, 'GCEvent')) {
83+
ListWrapper.push(normalizedEvents, normalizeEvent(event, {
84+
'name': 'gc',
85+
'args': {
86+
'usedHeapSize': isPresent(args['usedHeapSizeAfter']) ? args['usedHeapSizeAfter'] : args['usedHeapSizeBefore']
87+
}
88+
}));
11989
}
120-
} else if (StringWrapper.equals(type, 'GCEvent')) {
121-
ListWrapper.push(events, {
122-
'name': 'gc',
123-
'ts': startTime,
124-
'ph': 'B'
125-
});
126-
endEvent = {
127-
'name': 'gc',
128-
'ts': endTime,
129-
'ph': 'E',
130-
'args': {
131-
'amount': data['usedHeapSizeDelta']
132-
}
133-
};
134-
}
135-
if (isPresent(record['children'])) {
136-
this._convertPerfRecordsToEvents(record['children'], events);
137-
}
138-
if (isPresent(endEvent)) {
139-
ListWrapper.push(events, endEvent);
90+
} else if (StringWrapper.equals(cat, 'blink.console')) {
91+
ListWrapper.push(normalizedEvents, normalizeEvent(event, {
92+
'name': name
93+
}));
14094
}
14195
});
142-
return events;
96+
return normalizedEvents;
97+
}
98+
}
99+
100+
function normalizeEvent(chromeEvent, data) {
101+
var ph = chromeEvent['ph'];
102+
if (StringWrapper.equals(ph, 'S')) {
103+
ph = 'b';
104+
} else if (StringWrapper.equals(ph, 'F')) {
105+
ph = 'e';
106+
}
107+
var result = {
108+
'pid': chromeEvent['pid'],
109+
'ph': ph,
110+
'cat': 'timeline',
111+
'ts': chromeEvent['ts'] / 1000
112+
};
113+
if (chromeEvent['ph'] === 'X') {
114+
result['dur'] = chromeEvent['dur'] / 1000;
143115
}
116+
StringMapWrapper.forEach(data, (value, prop) => {
117+
result[prop] = value;
118+
});
119+
return result;
144120
}
145121

146122
var _BINDINGS = [

0 commit comments

Comments
 (0)