Skip to content

Commit 0448e65

Browse files
committed
Address comments from PR #174
1 parent 7a05df4 commit 0448e65

File tree

4 files changed

+105
-44
lines changed

4 files changed

+105
-44
lines changed

src/runner.ts

+84-38
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {ResultStatsWithDifferences, horizonsResolved, summaryStats, computeDiffe
2424
import {verticalTermResultTable, horizontalTermResultTable, verticalHtmlResultTable, horizontalHtmlResultTable, automaticResultTable, spinner, benchmarkOneLiner} from './format';
2525
import {Config} from './config';
2626
import * as github from './github';
27-
import {Server} from './server';
27+
import {Server, Session} from './server';
2828
import {specUrl} from './specs';
2929
import {wait} from './util';
3030

@@ -41,6 +41,24 @@ export class Runner {
4141
private readonly browsers = new Map<string, Browser>();
4242
private readonly bar: ProgressBar;
4343
private readonly results = new Map<BenchmarkSpec, BenchmarkResult[]>();
44+
45+
/**
46+
* How many times we will load a page and try to collect all measurements
47+
* before fully failing.
48+
*/
49+
private readonly maxAttempts = 3;
50+
51+
/**
52+
* Maximum milliseconds we will wait for all measurements to be collected per
53+
* attempt before reloading and trying a new attempt.
54+
*/
55+
private readonly attemptTimeout = 10000;
56+
57+
/**
58+
* How many milliseconds we will wait between each poll for measurements.
59+
*/
60+
private readonly pollTime = 50;
61+
4462
private completeGithubCheck?: (markdown: string) => void;
4563
private hitTimeout = false;
4664

@@ -120,10 +138,21 @@ export class Runner {
120138
this.results.set(spec, specResults);
121139
}
122140

141+
// This function is called once per page per sample. The first time this
142+
// function is called for a page, that result object becomes our "primary"
143+
// one. On subsequent calls, we accrete the additional sample data into this
144+
// primary one. The other fields are always the same, so we can just ignore
145+
// them after the first call.
146+
147+
// TODO(aomarks) The other fields (user agent, bytes sent, etc.) only need
148+
// to be collected on the first run of each page, so we could do that in the
149+
// warmup phase, and then function would only need to take sample data,
150+
// since it's a bit confusing how we throw away a bunch of fields after the
151+
// first call.
123152
for (const newResult of newResults) {
124-
const primary = specResults[newResult.measurementIdx];
153+
const primary = specResults[newResult.measurementIndex];
125154
if (primary === undefined) {
126-
specResults[newResult.measurementIdx] = newResult;
155+
specResults[newResult.measurementIndex] = newResult;
127156
} else {
128157
primary.millis.push(...newResult.millis);
129158
}
@@ -207,29 +236,38 @@ export class Runner {
207236
const {driver, initialTabHandle} =
208237
browsers.get(browserSignature(spec.browser))!;
209238

210-
let bytesSent = 0;
211-
let userAgent = '';
212-
// TODO(aomarks) Make maxAttempts and timeouts configurable.
213-
const maxAttempts = 3;
214-
const measurements = spec.measurement;
215-
let millis: number[];
216-
let numPending: number;
217-
for (let attempt = 1;; attempt++) {
218-
millis = [];
219-
numPending = measurements.length;
239+
let session: Session;
240+
let pendingMeasurements;
241+
let measurementResults: number[];
242+
243+
// We'll try N attempts per page. Within each attempt, we'll try to collect
244+
// all of the measurements by polling. If we hit our per-attempt timeout
245+
// before collecting all measurements, we'll move onto the next attempt
246+
// where we reload the whole page and start from scratch. If we hit our max
247+
// attempts, we'll throw.
248+
for (let pageAttempt = 1;; pageAttempt++) {
249+
// New attempt. Reset all measurements and results.
250+
pendingMeasurements = new Set(spec.measurement);
251+
measurementResults = [];
220252
await openAndSwitchToNewTab(driver, spec.browser);
221253
await driver.get(url);
222-
for (let waited = 0; numPending > 0 && waited <= 10000; waited += 50) {
254+
for (let waited = 0;
255+
pendingMeasurements.size > 0 && waited <= this.attemptTimeout;
256+
waited += this.pollTime) {
223257
// TODO(aomarks) You don't have to wait in callback mode!
224-
await wait(50);
225-
for (let i = 0; i < measurements.length; i++) {
226-
if (millis[i] !== undefined) {
258+
await wait(this.pollTime);
259+
for (let measurementIndex = 0;
260+
measurementIndex < spec.measurement.length;
261+
measurementIndex++) {
262+
if (measurementResults[measurementIndex] !== undefined) {
263+
// Already collected this measurement on this attempt.
227264
continue;
228265
}
229-
const result = await measure(driver, measurements[i], server);
266+
const measurement = spec.measurement[measurementIndex];
267+
const result = await measure(driver, measurement, server);
230268
if (result !== undefined) {
231-
millis[i] = result;
232-
numPending--;
269+
measurementResults[measurementIndex] = result;
270+
pendingMeasurements.delete(measurement);
233271
}
234272
}
235273
}
@@ -240,43 +278,51 @@ export class Runner {
240278
await driver.switchTo().window(initialTabHandle);
241279

242280
if (server !== undefined) {
243-
const session = server.endSession();
244-
bytesSent = session.bytesSent;
245-
userAgent = session.userAgent;
281+
session = server.endSession();
246282
}
247283

248-
if (numPending === 0 || attempt >= maxAttempts) {
284+
if (pendingMeasurements.size === 0 || pageAttempt >= this.maxAttempts) {
249285
break;
250286
}
251287

252288
console.log(
253-
`\n\nFailed ${attempt}/${maxAttempts} times ` +
254-
`to get a measurement ` +
255-
`in ${spec.browser.name} from ${url}. Retrying.`);
289+
`\n\nFailed ${pageAttempt}/${this.maxAttempts} times ` +
290+
`to get measurement(s) ${spec.name}` +
291+
(spec.measurement.length > 1 ? ` [${
292+
[...pendingMeasurements]
293+
.map(measurementName)
294+
.join(', ')}]` :
295+
'') +
296+
` in ${spec.browser.name} from ${url}. Retrying.`);
256297
}
257298

258-
if (numPending > 0) {
299+
if (pendingMeasurements.size > 0) {
259300
console.log();
260301
throw new Error(
261-
`\n\nFailed ${maxAttempts}/${maxAttempts} times ` +
262-
`to get a measurement ` +
263-
`in ${spec.browser.name} from ${url}. Retrying.`);
302+
`\n\nFailed ${this.maxAttempts}/${this.maxAttempts} times ` +
303+
`to get measurement(s) ${spec.name}` +
304+
(spec.measurement.length > 1 ? ` [${
305+
[...pendingMeasurements]
306+
.map(measurementName)
307+
.join(', ')}]` :
308+
'') +
309+
` in ${spec.browser.name} from ${url}`);
264310
}
265311

266-
return measurements.map(
267-
(measurement, measurementIdx) => ({
268-
name: measurements.length === 1 ?
312+
return spec.measurement.map(
313+
(measurement, measurementIndex) => ({
314+
name: spec.measurement.length === 1 ?
269315
spec.name :
270316
`${spec.name} [${measurementName(measurement)}]`,
271-
measurementIdx,
317+
measurementIndex: measurementIndex,
272318
queryString: spec.url.kind === 'local' ? spec.url.queryString : '',
273319
version: spec.url.kind === 'local' && spec.url.version !== undefined ?
274320
spec.url.version.label :
275321
'',
276-
millis: [millis[measurementIdx]],
277-
bytesSent,
322+
millis: [measurementResults[measurementIndex]],
323+
bytesSent: session ? session.bytesSent : 0,
278324
browser: spec.browser,
279-
userAgent,
325+
userAgent: session ? session.userAgent : '',
280326
}));
281327
}
282328

src/server.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface MountPoint {
4242

4343
const clientLib = path.resolve(__dirname, '..', 'client', 'lib');
4444

45-
interface Session {
45+
export interface Session {
4646
bytesSent: number;
4747
userAgent: string;
4848
}

src/test/test_helpers.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ export async function fakeResults(configFile: ConfigFile):
5151
...new Array(Math.floor(config.sampleSize / 2)).fill(averageMillis - 5),
5252
...new Array(Math.ceil(config.sampleSize / 2)).fill(averageMillis + 5),
5353
];
54-
for (let measurementIdx = 0; measurementIdx < measurement.length;
55-
measurementIdx++) {
54+
for (let measurementIndex = 0; measurementIndex < measurement.length;
55+
measurementIndex++) {
5656
results.push({
5757
stats: summaryStats(millis),
5858
result: {
5959
name,
60-
measurementIdx,
60+
measurementIndex,
6161
queryString: url.kind === 'local' ? url.queryString : '',
6262
version: url.kind === 'local' && url.version !== undefined ?
6363
url.version.label :

src/types.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,27 @@ export interface BenchmarkResponse {
9494
millis: number;
9595
}
9696

97+
/**
98+
* Benchmark results for a particular measurement on a particular page, across
99+
* all samples.
100+
*/
97101
export interface BenchmarkResult {
102+
/**
103+
* Label for this result. When there is more than one per page, this will
104+
* contain both the page and measurement labels as "page [measurement]".
105+
*/
98106
name: string;
99-
measurementIdx: number;
107+
/**
108+
* A single page can return multiple measurements. The offset into the array
109+
* of measurements in the spec that this particular result corresponds to.
110+
*/
111+
measurementIndex: number;
112+
/**
113+
* Millisecond measurements for each sample.
114+
*/
115+
millis: number[];
100116
queryString: string;
101117
version: string;
102-
millis: number[];
103118
browser: BrowserConfig;
104119
userAgent: string;
105120
bytesSent: number;

0 commit comments

Comments
 (0)