browsertime 17.8.1 → 17.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Browsertime changelog (we do [semantic versioning](https://semver.org))
2
2
 
3
+ ## 17.9.0 - 2022-04-19
4
+ ### Added
5
+ * New command to start geckoprofiling when scripting using Firefox. Thank you [KS](https://github.com/92kns) for PR [#1934](https://github.com/sitespeedio/browsertime/pull/1934). This is probably only something you need if you work at Mozilla. There's also a follow PR [#1940](https://github.com/sitespeedio/browsertime/pull/1940) that fixes some of the issues I've been wanted to fix with the Geckoprofiler code.
6
+
7
+ * Use `--enableProfileRun` to make one extra run where metrics aren't collected but Geckoprofiler or Chromes timeline log is turned on. This useful if you are worrying about the trace causing overhead, but you still wan trace from the test/journey. It's a little rough at the moment so it will a follow up in the future [#1943](https://github.com/sitespeedio/browsertime/pull/1943).
8
+
3
9
  ## 17.8.1 - 2022-04-12
4
10
  ### Fixed
5
11
  * Upgraded to Firefox HAR 0.0.10 [#1939](https://github.com/sitespeedio/browsertime/pull/1939).
@@ -122,6 +122,28 @@ async function run(urls, options) {
122
122
  storageManager.writeJson(harName + '.har', result.har, useGzip)
123
123
  );
124
124
  }
125
+
126
+ if (options.enableProfileRun) {
127
+ log.info('Make one extra run to collect trace information');
128
+ options.iterations = 1;
129
+ if (options.browser === 'firefox') {
130
+ options.firefox.geckoProfiler = true;
131
+ } else if (options.browser === 'chrome') {
132
+ options.chrome.timeline = true;
133
+ options.cpu = true;
134
+ options.chrome.enableTraceScreenshots = true;
135
+ options.chrome.traceCategory = [
136
+ 'disabled-by-default-v8.cpu_profiler'
137
+ ];
138
+ }
139
+ options.video = false;
140
+ options.visualMetrics = false;
141
+ const traceEngine = new Engine(options);
142
+ await traceEngine.start();
143
+ await traceEngine.runMultiple(urls, scriptsByCategory);
144
+ await traceEngine.stop();
145
+ }
146
+
125
147
  await Promise.all(saveOperations);
126
148
 
127
149
  const resultDirectory = relative(process.cwd(), storageManager.directory);
@@ -331,7 +331,11 @@ export class Chromium {
331
331
 
332
332
  if (this.collectTracingEvents) {
333
333
  const trace = parse(this.events, result.url);
334
- result.extraJson[`trace-${index}.json`] = trace;
334
+ const name = this.options.enableProfileRun
335
+ ? `trace-${index}-extra-run.json`
336
+ : `trace-${index}.json`;
337
+ result.extraJson[name] = trace;
338
+
335
339
  const cpu = await parseCPUTrace(trace, result.url);
336
340
  result.cpu = cpu;
337
341
 
@@ -503,8 +503,11 @@ export class Collector {
503
503
  }
504
504
 
505
505
  if (this.options.firefox && this.options.firefox.geckoProfiler) {
506
+ const name = this.options.enableProfileRun
507
+ ? `geckoProfile-${index}-extra.json.gz`
508
+ : `geckoProfile-${index}.json.gz`;
506
509
  results.files.geckoProfiles.push(
507
- `${pathToFolder(url, this.options)}geckoProfile-${index}.json.gz`
510
+ `${pathToFolder(url, this.options)}${name}`
508
511
  );
509
512
  }
510
513
 
@@ -0,0 +1,38 @@
1
+ import intel from 'intel';
2
+ const log = intel.getLogger('browsertime.command.geckoprofiler');
3
+ export class GeckoProfiler {
4
+ constructor(GeckoProfiler, browser, index, options) {
5
+ this.GeckoProfiler = GeckoProfiler;
6
+ this.browser = browser;
7
+ this.index = index;
8
+ this.options = options;
9
+ }
10
+
11
+ async start() {
12
+ if (this.options.browser === 'firefox') {
13
+ if (this.options.firefox.geckoProfilerRecordingType === 'custom') {
14
+ return this.GeckoProfiler.start();
15
+ } else {
16
+ log.info(
17
+ 'You need to set geckoProfilerRecordingType to custom to turn on the profiler in scripting'
18
+ );
19
+ }
20
+ } else {
21
+ throw new Error('Geckoprofiler only works in Firefox');
22
+ }
23
+ }
24
+
25
+ async stop() {
26
+ if (this.options.browser === 'firefox') {
27
+ if (this.options.firefox.geckoProfilerRecordingType === 'custom') {
28
+ const url = await this.browser
29
+ .getDriver()
30
+ .executeScript('return document.documentURI;');
31
+
32
+ return this.GeckoProfiler.stop(this.index, url);
33
+ }
34
+ } else {
35
+ throw new Error('Geckoprofiler only works in Firefox');
36
+ }
37
+ }
38
+ }
@@ -381,7 +381,9 @@ export class Engine {
381
381
  }
382
382
  }
383
383
 
384
- logResultLogLine(totalResult);
384
+ if (!options.enableProfileRun) {
385
+ logResultLogLine(totalResult);
386
+ }
385
387
 
386
388
  if (failures.length > 0) {
387
389
  // If we have a result
@@ -41,6 +41,8 @@ import { getNumberOfRunningProcesses } from '../../support/processes.js';
41
41
  import { isAndroidConfigured, Android } from '../../android/index.js';
42
42
  import { Scroll } from './command/scroll.js';
43
43
  import { Navigation } from './command/navigation.js';
44
+ import { GeckoProfiler } from '../../firefox/geckoProfiler.js';
45
+ import { GeckoProfiler as GeckoProfilerCommand } from './command/geckoProfiler.js';
44
46
  const log = intel.getLogger('browsertime');
45
47
  const delay = ms => new Promise(res => setTimeout(res, ms));
46
48
 
@@ -147,6 +149,17 @@ export class Iteration {
147
149
  options
148
150
  );
149
151
 
152
+ const browserProfiler = new GeckoProfiler(
153
+ browser,
154
+ this.storageManager,
155
+ this.options
156
+ );
157
+ const profiler = new GeckoProfilerCommand(
158
+ browserProfiler,
159
+ browser,
160
+ index,
161
+ this.options
162
+ );
150
163
  const cdp = new ChromeDevelopmentToolsProtocol(
151
164
  engineDelegate,
152
165
  options.browser
@@ -154,6 +167,7 @@ export class Iteration {
154
167
  const android = new Android(options);
155
168
  const debug = new Debug(browser, options);
156
169
  const commands = {
170
+ profiler: profiler,
157
171
  click: new Click(browser, this.pageCompleteCheck),
158
172
  scroll: new Scroll(browser, options),
159
173
  addText: new AddText(browser),
@@ -1,10 +1,7 @@
1
1
  import { join } from 'node:path';
2
2
  import get from 'lodash.get';
3
3
  import intel from 'intel';
4
- import {
5
- android_sampling_interval,
6
- desktop_sampling_interval
7
- } from './settings/geckoProfilerDefaults.js';
4
+ import { geckoProfilerDefaults } from './settings/geckoProfilerDefaults.js';
8
5
  import { isAndroidConfigured, Android } from '../android/index.js';
9
6
  import { pathToFolder } from '../support/pathToFolder.js';
10
7
  import { BrowserError } from '../support/errors.js';
@@ -33,10 +30,10 @@ async function timeout(promise, ms, errorMessage) {
33
30
  ]);
34
31
  }
35
32
  export class GeckoProfiler {
36
- constructor(runner, storageManager, firefoxConfig, options) {
33
+ constructor(runner, storageManager, options) {
37
34
  this.runner = runner;
38
35
  this.storageManager = storageManager;
39
- this.firefoxConfig = firefoxConfig;
36
+ this.firefoxConfig = options.firefox;
40
37
  this.options = options;
41
38
  }
42
39
 
@@ -44,14 +41,26 @@ export class GeckoProfiler {
44
41
  const runner = this.runner;
45
42
  const firefoxConfig = this.firefoxConfig;
46
43
  const options = this.options;
47
- const chosenFeatures =
48
- firefoxConfig.geckoProfilerParams.features.split(',');
44
+ const chosenFeatures = get(
45
+ firefoxConfig,
46
+ 'geckoProfilerParams.features',
47
+ geckoProfilerDefaults.features
48
+ ).split(',');
49
+
49
50
  const featureString = '["' + chosenFeatures.join('","') + '"]';
50
51
 
51
- const chosenThreads = firefoxConfig.geckoProfilerParams.threads.split(',');
52
+ const chosenThreads = get(
53
+ firefoxConfig,
54
+ 'geckoProfilerParams.threads',
55
+ geckoProfilerDefaults.threads
56
+ ).split(',');
52
57
  const threadString = '["' + chosenThreads.join('","') + '"]';
53
58
 
54
- let interval = firefoxConfig.geckoProfilerParams.interval;
59
+ let interval = get(
60
+ firefoxConfig,
61
+ 'geckoProfilerParams.interval',
62
+ geckoProfilerDefaults.interval
63
+ );
55
64
 
56
65
  // Set platform specific sampling intervals if not explicitly specified.
57
66
  if (
@@ -59,11 +68,15 @@ export class GeckoProfiler {
59
68
  !firefoxConfig.geckoProfilerParams.interval
60
69
  ) {
61
70
  interval = isAndroidConfigured(options)
62
- ? android_sampling_interval
63
- : desktop_sampling_interval;
71
+ ? geckoProfilerDefaults.androidSamplingInterval
72
+ : geckoProfilerDefaults.desktopSamplingInterval;
64
73
  }
65
74
 
66
- const bufferSize = firefoxConfig.geckoProfilerParams.bufferSize;
75
+ const bufferSize = get(
76
+ firefoxConfig,
77
+ 'geckoProfilerParams.bufferSize',
78
+ geckoProfilerDefaults.bufferSize
79
+ );
67
80
 
68
81
  // Firefox 69 and above has a slightly different API for StartProfiler
69
82
  // See nsIProfiler::StartProfiler in tools/profiler/gecko/nsIProfiler.idl
@@ -121,6 +134,7 @@ export class GeckoProfiler {
121
134
  const runner = this.runner;
122
135
  const storageManager = this.storageManager;
123
136
  const options = this.options;
137
+
124
138
  let profileDir = await storageManager.createSubDataDir(
125
139
  join(pathToFolder(url, options))
126
140
  );
@@ -171,12 +185,11 @@ export class GeckoProfiler {
171
185
 
172
186
  // GZIP the profile and remove the old file
173
187
  log.info('Gzip file the profile.');
188
+ const name = this.options.enableProfileRun
189
+ ? `geckoProfile-${index}-extra.json.gz`
190
+ : `geckoProfile-${index}.json.gz`;
174
191
  await timeout(
175
- storageManager.gzip(
176
- destinationFilename,
177
- join(profileDir, `geckoProfile-${index}.json.gz`),
178
- true
179
- ),
192
+ storageManager.gzip(destinationFilename, join(profileDir, name), true),
180
193
  300_000, // 5 minutes
181
194
  'Could not gzip the profile.'
182
195
  );
@@ -1,5 +1,7 @@
1
- export const features = 'js,stackwalk,leaf';
2
- export const threads = 'GeckoMain,Compositor,Renderer';
3
- export const desktop_sampling_interval = 1;
4
- export const android_sampling_interval = 4;
5
- export const bufferSize = 13_107_200; // 100MB;
1
+ export const geckoProfilerDefaults = {
2
+ features: 'js,stackwalk,leaf',
3
+ threads: 'GeckoMain,Compositor,Renderer',
4
+ desktopSamplingInterval: 1,
5
+ androidSamplingInterval: 4,
6
+ bufferSize: 13_107_200 // 100MB
7
+ };
@@ -101,11 +101,13 @@ export class Firefox {
101
101
  await this.android.resetPowerUsage();
102
102
  }
103
103
 
104
- if (this.firefoxConfig.geckoProfiler) {
104
+ if (
105
+ this.firefoxConfig.geckoProfiler &&
106
+ this.firefoxConfig.geckoProfilerRecordingType !== 'custom'
107
+ ) {
105
108
  this.geckoProfiler = new GeckoProfiler(
106
109
  runner,
107
110
  this.storageManager,
108
- this.firefoxConfig,
109
111
  this.options
110
112
  );
111
113
 
@@ -133,7 +135,10 @@ export class Firefox {
133
135
  );
134
136
  }
135
137
 
136
- if (this.firefoxConfig.geckoProfiler) {
138
+ if (
139
+ this.firefoxConfig.geckoProfiler &&
140
+ this.firefoxConfig.geckoProfilerRecordingType !== 'custom'
141
+ ) {
137
142
  await this.geckoProfiler.stop(index, url);
138
143
  }
139
144
 
@@ -14,13 +14,7 @@ import {
14
14
  xvfbDisplay
15
15
  } from '../video/defaults.js';
16
16
  import { screenshotDefaults } from '../screenshot/defaults.js';
17
- import {
18
- features,
19
- threads as _threads,
20
- desktop_sampling_interval,
21
- android_sampling_interval,
22
- bufferSize
23
- } from '../firefox/settings/geckoProfilerDefaults.js';
17
+ import { geckoProfilerDefaults } from '../firefox/settings/geckoProfilerDefaults.js';
24
18
 
25
19
  const configPath = findUpSync(['.browsertime.json']);
26
20
 
@@ -472,26 +466,33 @@ export function parseCommandLine() {
472
466
  type: 'boolean',
473
467
  group: 'firefox'
474
468
  })
469
+ .option('firefox.geckoProfilerRecordingType', {
470
+ describe: 'Expose the start/stop commands for the gecko profiler',
471
+ default: 'pageload',
472
+ choices: ['pageload', 'custom'],
473
+ type: 'string',
474
+ group: 'firefox'
475
+ })
475
476
  .option('firefox.geckoProfilerParams.features', {
476
477
  describe: 'Enabled features during gecko profiling',
477
- default: features,
478
+ default: geckoProfilerDefaults.features,
478
479
  type: 'string',
479
480
  group: 'firefox'
480
481
  })
481
482
  .option('firefox.geckoProfilerParams.threads', {
482
483
  describe: 'Threads to profile.',
483
- default: _threads,
484
+ default: geckoProfilerDefaults.threads,
484
485
  type: 'string',
485
486
  group: 'firefox'
486
487
  })
487
488
  .option('firefox.geckoProfilerParams.interval', {
488
- describe: `Sampling interval in ms. Defaults to ${desktop_sampling_interval} on desktop, and ${android_sampling_interval} on android.`,
489
+ describe: `Sampling interval in ms. Defaults to ${geckoProfilerDefaults.desktopSamplingInterval} on desktop, and ${geckoProfilerDefaults.androidSamplingInterval} on android.`,
489
490
  type: 'number',
490
491
  group: 'firefox'
491
492
  })
492
493
  .option('firefox.geckoProfilerParams.bufferSize', {
493
494
  describe: 'Buffer size in elements. Default is ~90MB.',
494
- default: bufferSize,
495
+ default: geckoProfilerDefaults.bufferSize,
495
496
  type: 'number',
496
497
  group: 'firefox'
497
498
  })
@@ -578,6 +579,11 @@ export function parseCommandLine() {
578
579
  'URL to a running Selenium server (e.g. to run a browser on another machine).',
579
580
  group: 'selenium'
580
581
  })
582
+ .option('enableProfileRun', {
583
+ type: 'boolean',
584
+ describe:
585
+ 'Make one extra run that collects the profiling trace log (no other metrics is collected). For Chrome it will collect the timeline trace, for Firefox it will get the Geckoprofiler trace. This means you do not need to get the trace for all runs and can skip the overhead it produces.'
586
+ })
581
587
  .option('video', {
582
588
  type: 'boolean',
583
589
  describe:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "browsertime",
3
3
  "description": "Get performance metrics from your web page using Browsertime.",
4
- "version": "17.8.1",
4
+ "version": "17.9.0",
5
5
  "bin": "./bin/browsertime.js",
6
6
  "type": "module",
7
7
  "dependencies": {
@@ -15,9 +15,9 @@
15
15
  "ff-test-bidi-har-export": "0.0.10",
16
16
  "btoa": "1.2.1",
17
17
  "chrome-har": "0.13.1",
18
- "chrome-remote-interface": "0.32.1",
18
+ "chrome-remote-interface": "0.32.2",
19
19
  "dayjs": "1.11.7",
20
- "execa": "7.0.0",
20
+ "execa": "7.1.1",
21
21
  "fast-stats": "0.0.6",
22
22
  "find-up": "6.3.0",
23
23
  "get-port": "6.1.2",