Skip to content

Commit 29d75d2

Browse files
committed
Improve representation of measurements
1 parent 3d885a7 commit 29d75d2

16 files changed

+385
-168
lines changed

CHANGELOG.md

+20-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,26 @@ project adheres to [Semantic Versioning](http://semver.org/).
1414
"benchmarks": [
1515
{
1616
"measurement": {
17-
"performanceEntry": {
18-
"name": "foo"
19-
}
17+
"kind": "performance",
18+
"entryName": "foo"
19+
}
20+
}
21+
]
22+
```
23+
24+
- Add new syntax for specifying benchmarks:
25+
26+
```
27+
"benchmarks": [
28+
{
29+
"measurement": {
30+
"kind": "callback"
31+
}
32+
},
33+
{
34+
"measurement": {
35+
"kind": "expression",
36+
"expression": "window.tachometerResult"
2037
}
2138
}
2239
]

README.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,8 @@ And in your config file:
107107
"benchmarks": [
108108
{
109109
"measurement": {
110-
"performanceEntry": {
111-
"name": "foo"
112-
}
110+
"kind": "performance",
111+
"entryName": "foo"
113112
}
114113
}
115114
]

config.schema.json

+51-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
"$schema": "http://json-schema.org/draft-07/schema#",
33
"additionalProperties": false,
44
"definitions": {
5+
"CallbackMeasurement": {
6+
"additionalProperties": false,
7+
"properties": {
8+
"kind": {
9+
"enum": [
10+
"callback"
11+
],
12+
"type": "string"
13+
}
14+
},
15+
"required": [
16+
"kind"
17+
],
18+
"type": "object"
19+
},
520
"ChromeConfig": {
621
"additionalProperties": false,
722
"properties": {
@@ -90,16 +105,13 @@
90105
"measurement": {
91106
"anyOf": [
92107
{
93-
"additionalProperties": false,
94-
"properties": {
95-
"performanceEntry": {
96-
"$ref": "#/definitions/PerformanceEntryCriteria"
97-
}
98-
},
99-
"required": [
100-
"performanceEntry"
101-
],
102-
"type": "object"
108+
"$ref": "#/definitions/CallbackMeasurement"
109+
},
110+
{
111+
"$ref": "#/definitions/PerformanceEntryMeasurement"
112+
},
113+
{
114+
"$ref": "#/definitions/ExpressionMeasurement"
103115
},
104116
{
105117
"enum": [
@@ -173,6 +185,25 @@
173185
],
174186
"type": "object"
175187
},
188+
"ExpressionMeasurement": {
189+
"additionalProperties": false,
190+
"properties": {
191+
"expression": {
192+
"type": "string"
193+
},
194+
"kind": {
195+
"enum": [
196+
"expression"
197+
],
198+
"type": "string"
199+
}
200+
},
201+
"required": [
202+
"expression",
203+
"kind"
204+
],
205+
"type": "object"
206+
},
176207
"FirefoxConfig": {
177208
"additionalProperties": false,
178209
"properties": {
@@ -254,16 +285,22 @@
254285
"description": "A mapping from NPM package name to version specifier, as used in a\npackage.json's \"dependencies\" and \"devDependencies\".",
255286
"type": "object"
256287
},
257-
"PerformanceEntryCriteria": {
288+
"PerformanceEntryMeasurement": {
258289
"additionalProperties": false,
259-
"description": "Criteria for matching a Performance Entry.",
260290
"properties": {
261-
"name": {
291+
"entryName": {
292+
"type": "string"
293+
},
294+
"kind": {
295+
"enum": [
296+
"performance"
297+
],
262298
"type": "string"
263299
}
264300
},
265301
"required": [
266-
"name"
302+
"entryName",
303+
"kind"
267304
],
268305
"type": "object"
269306
},

src/automatic.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,9 @@ export class AutomaticMode {
202202
await driver.get(url);
203203
for (let waited = 0; millis === undefined && waited <= 10000;
204204
waited += 50) {
205+
// TODO(aomarks) You don't have to wait in callback mode!
205206
await wait(50);
206-
millis = await measure(driver, spec, server);
207+
millis = await measure(driver, spec.measurement, server);
207208
}
208209

209210
// Close the active tab (but not the whole browser, since the
@@ -223,21 +224,15 @@ export class AutomaticMode {
223224

224225
console.log(
225226
`\n\nFailed ${attempt}/${maxAttempts} times ` +
226-
`to get a ${spec.measurement} measurement ` +
227-
(spec.measurement === 'global' ?
228-
`(from '${spec.measurementExpression}') ` :
229-
'') +
227+
`to get a measurement ` +
230228
`in ${spec.browser.name} from ${url}. Retrying.`);
231229
}
232230

233231
if (millis === undefined) {
234232
console.log();
235233
throw new Error(
236234
`\n\nFailed ${maxAttempts}/${maxAttempts} times ` +
237-
`to get a ${spec.measurement} measurement ` +
238-
(spec.measurement === 'global' ?
239-
`(from '${spec.measurementExpression}') ` :
240-
'') +
235+
`to get a measurement ` +
241236
`in ${spec.browser.name} from ${url}. Retrying.`);
242237
}
243238

src/config.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ export async function makeConfig(opts: Opts): Promise<Config> {
123123
}
124124

125125
for (const spec of config.benchmarks) {
126-
if (spec.measurement === 'fcp' && !fcpBrowsers.has(spec.browser.name)) {
126+
if (spec.measurement.kind === 'performance' &&
127+
spec.measurement.entryName === 'first-contentful-paint' &&
128+
!fcpBrowsers.has(spec.browser.name)) {
127129
throw new Error(
128130
`Browser ${spec.browser.name} does not support the ` +
129131
`first contentful paint (FCP) measurement`);

src/configfile.ts

+20-15
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ interface ConfigFileBenchmark {
114114
* }
115115
* }
116116
*/
117-
measurement?: Measurement;
117+
measurement?: ConfigFileMeasurement;
118118

119119
/**
120120
* Expression to use to retrieve global result. Defaults to
@@ -136,6 +136,8 @@ interface ConfigFileBenchmark {
136136
expand?: ConfigFileBenchmark[];
137137
}
138138

139+
type ConfigFileMeasurement = 'callback'|'fcp'|'global'|Measurement;
140+
139141
type BrowserConfigs =
140142
ChromeConfig|FirefoxConfig|SafariConfig|EdgeConfig|IEConfig;
141143

@@ -324,13 +326,24 @@ async function parseBenchmark(benchmark: ConfigFileBenchmark, root: string):
324326
spec.browser = browser;
325327
}
326328

327-
if (benchmark.measurement !== undefined) {
329+
if (benchmark.measurement === 'callback') {
330+
spec.measurement = {
331+
kind: 'callback',
332+
};
333+
} else if (benchmark.measurement === 'fcp') {
334+
spec.measurement = {
335+
kind: 'performance',
336+
entryName: 'first-contentful-paint',
337+
};
338+
} else if (benchmark.measurement === 'global') {
339+
spec.measurement = {
340+
kind: 'expression',
341+
expression:
342+
benchmark.measurementExpression || defaults.measurementExpression,
343+
};
344+
} else {
328345
spec.measurement = benchmark.measurement;
329346
}
330-
if (spec.measurement === 'global' &&
331-
benchmark.measurementExpression !== undefined) {
332-
spec.measurementExpression = benchmark.measurementExpression;
333-
}
334347

335348
const url = benchmark.url;
336349
if (url !== undefined) {
@@ -446,15 +459,7 @@ function applyDefaults(partialSpec: Partial<BenchmarkSpec>): BenchmarkSpec {
446459
if (measurement === undefined) {
447460
measurement = defaults.measurement(url);
448461
}
449-
const spec: BenchmarkSpec = {name, url, browser, measurement};
450-
if (measurement === 'global') {
451-
if (partialSpec.measurementExpression === undefined) {
452-
spec.measurementExpression = defaults.measurementExpression;
453-
} else {
454-
spec.measurementExpression = partialSpec.measurementExpression;
455-
}
456-
}
457-
return spec;
462+
return {name, url, browser, measurement};
458463
}
459464

460465
export async function writeBackSchemaIfNeeded(

src/defaults.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ export const measurementExpression = 'window.tachometerResult';
2727

2828
export function measurement(url: LocalUrl|RemoteUrl): Measurement {
2929
if (url.kind === 'remote') {
30-
return 'fcp';
30+
return {
31+
kind: 'performance',
32+
entryName: 'first-contentful-paint',
33+
};
3134
}
32-
return 'callback';
35+
return {kind: 'callback'};
3336
}

src/flags.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as path from 'path';
1414

1515
import {supportedBrowsers} from './browser';
1616
import * as defaults from './defaults';
17-
import {Measurement, measurements} from './types';
17+
import {CommandLineMeasurements, measurements} from './types';
1818

1919
import commandLineArgs = require('command-line-args');
2020
import commandLineUsage = require('command-line-usage');
@@ -216,7 +216,7 @@ export interface Opts {
216216
'sample-size': number|undefined;
217217
manual: boolean;
218218
save: string;
219-
measure: Measurement|undefined;
219+
measure: CommandLineMeasurements|undefined;
220220
'measurement-expression': string|undefined;
221221
horizon: string|undefined;
222222
timeout: number|undefined;

src/measure.ts

+14-21
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import * as webdriver from 'selenium-webdriver';
1313

1414
import {Server} from './server';
15-
import {BenchmarkSpec, PerformanceEntryCriteria} from './types';
15+
import {Measurement, PerformanceEntryMeasurement} from './types';
1616
import {escapeStringLiteral, throwUnreachable} from './util';
1717

1818
/**
@@ -22,25 +22,18 @@ import {escapeStringLiteral, throwUnreachable} from './util';
2222
*/
2323
export async function measure(
2424
driver: webdriver.WebDriver,
25-
{measurement, measurementExpression}: BenchmarkSpec,
25+
measurement: Measurement,
2626
server: Server|undefined): Promise<number|undefined> {
27-
if (measurement === 'fcp') {
28-
return queryForPerformanceEntry(driver, {name: 'first-contentful-paint'});
29-
}
30-
if (measurement === 'callback') {
31-
if (server === undefined) {
32-
throw new Error('Internal error: no server for spec');
33-
}
34-
return (await server.nextResults()).millis;
35-
}
36-
if (measurement === 'global') {
37-
if (!measurementExpression) {
38-
throw new Error('Internal error: no measurement expression');
39-
}
40-
return queryForExpression(driver, measurementExpression);
41-
}
42-
if (measurement && 'performanceEntry' in measurement) {
43-
return queryForPerformanceEntry(driver, measurement.performanceEntry);
27+
switch (measurement.kind) {
28+
case 'callback':
29+
if (server === undefined) {
30+
throw new Error('Internal error: no server for spec');
31+
}
32+
return (await server.nextResults()).millis;
33+
case 'expression':
34+
return queryForExpression(driver, measurement.expression);
35+
case 'performance':
36+
return queryForPerformanceEntry(driver, measurement);
4437
}
4538
throwUnreachable(
4639
measurement,
@@ -71,8 +64,8 @@ interface PerformanceEntry {
7164
*/
7265
async function queryForPerformanceEntry(
7366
driver: webdriver.WebDriver,
74-
criteria: PerformanceEntryCriteria): Promise<number|undefined> {
75-
const escaped = escapeStringLiteral(criteria.name);
67+
measurement: PerformanceEntryMeasurement): Promise<number|undefined> {
68+
const escaped = escapeStringLiteral(measurement.entryName);
7669
const script = `return window.performance.getEntriesByName(\`${escaped}\`);`;
7770
const entries = await driver.executeScript(script) as PerformanceEntry[];
7871
if (entries.length === 0) {

0 commit comments

Comments
 (0)