@@ -24,7 +24,7 @@ import {ResultStatsWithDifferences, horizonsResolved, summaryStats, computeDiffe
24
24
import { verticalTermResultTable , horizontalTermResultTable , verticalHtmlResultTable , horizontalHtmlResultTable , automaticResultTable , spinner , benchmarkOneLiner } from './format' ;
25
25
import { Config } from './config' ;
26
26
import * as github from './github' ;
27
- import { Server } from './server' ;
27
+ import { Server , Session } from './server' ;
28
28
import { specUrl } from './specs' ;
29
29
import { wait } from './util' ;
30
30
@@ -41,6 +41,24 @@ export class Runner {
41
41
private readonly browsers = new Map < string , Browser > ( ) ;
42
42
private readonly bar : ProgressBar ;
43
43
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
+
44
62
private completeGithubCheck ?: ( markdown : string ) => void ;
45
63
private hitTimeout = false ;
46
64
@@ -120,10 +138,21 @@ export class Runner {
120
138
this . results . set ( spec , specResults ) ;
121
139
}
122
140
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.
123
152
for ( const newResult of newResults ) {
124
- const primary = specResults [ newResult . measurementIdx ] ;
153
+ const primary = specResults [ newResult . measurementIndex ] ;
125
154
if ( primary === undefined ) {
126
- specResults [ newResult . measurementIdx ] = newResult ;
155
+ specResults [ newResult . measurementIndex ] = newResult ;
127
156
} else {
128
157
primary . millis . push ( ...newResult . millis ) ;
129
158
}
@@ -207,29 +236,38 @@ export class Runner {
207
236
const { driver, initialTabHandle} =
208
237
browsers . get ( browserSignature ( spec . browser ) ) ! ;
209
238
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 = [ ] ;
220
252
await openAndSwitchToNewTab ( driver , spec . browser ) ;
221
253
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 ) {
223
257
// 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.
227
264
continue ;
228
265
}
229
- const result = await measure ( driver , measurements [ i ] , server ) ;
266
+ const measurement = spec . measurement [ measurementIndex ] ;
267
+ const result = await measure ( driver , measurement , server ) ;
230
268
if ( result !== undefined ) {
231
- millis [ i ] = result ;
232
- numPending -- ;
269
+ measurementResults [ measurementIndex ] = result ;
270
+ pendingMeasurements . delete ( measurement ) ;
233
271
}
234
272
}
235
273
}
@@ -240,43 +278,51 @@ export class Runner {
240
278
await driver . switchTo ( ) . window ( initialTabHandle ) ;
241
279
242
280
if ( server !== undefined ) {
243
- const session = server . endSession ( ) ;
244
- bytesSent = session . bytesSent ;
245
- userAgent = session . userAgent ;
281
+ session = server . endSession ( ) ;
246
282
}
247
283
248
- if ( numPending === 0 || attempt >= maxAttempts ) {
284
+ if ( pendingMeasurements . size === 0 || pageAttempt >= this . maxAttempts ) {
249
285
break ;
250
286
}
251
287
252
288
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.` ) ;
256
297
}
257
298
258
- if ( numPending > 0 ) {
299
+ if ( pendingMeasurements . size > 0 ) {
259
300
console . log ( ) ;
260
301
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 } ` ) ;
264
310
}
265
311
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 ?
269
315
spec . name :
270
316
`${ spec . name } [${ measurementName ( measurement ) } ]` ,
271
- measurementIdx ,
317
+ measurementIndex : measurementIndex ,
272
318
queryString : spec . url . kind === 'local' ? spec . url . queryString : '' ,
273
319
version : spec . url . kind === 'local' && spec . url . version !== undefined ?
274
320
spec . url . version . label :
275
321
'' ,
276
- millis : [ millis [ measurementIdx ] ] ,
277
- bytesSent,
322
+ millis : [ measurementResults [ measurementIndex ] ] ,
323
+ bytesSent : session ? session . bytesSent : 0 ,
278
324
browser : spec . browser ,
279
- userAgent,
325
+ userAgent : session ? session . userAgent : '' ,
280
326
} ) ) ;
281
327
}
282
328
0 commit comments