browsertime 25.1.0 → 25.3.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 +15 -0
- package/lib/android/index.js +6 -1
- package/lib/chrome/har.js +14 -2
- package/lib/chrome/webdriver/chromium.js +3 -2
- package/lib/core/engine/command/perfetto.js +215 -0
- package/lib/core/engine/command/simpleperf.js +181 -0
- package/lib/core/engine/commands.js +19 -0
- package/lib/firefox/perfStats.js +39 -3
- package/lib/firefox/webdriver/firefox.js +12 -3
- package/lib/support/cli.js +21 -5
- package/lib/support/har/index.js +22 -0
- package/package.json +16 -16
- package/types/android/index.d.ts +1 -0
- package/types/android/index.d.ts.map +1 -1
- package/types/core/engine/command/perfetto.d.ts +43 -0
- package/types/core/engine/command/perfetto.d.ts.map +1 -0
- package/types/core/engine/command/simpleperf.d.ts +43 -0
- package/types/core/engine/command/simpleperf.d.ts.map +1 -0
- package/types/core/engine/commands.d.ts +12 -0
- package/types/core/engine/commands.d.ts.map +1 -1
- package/types/firefox/perfStats.d.ts +1 -1
- package/types/firefox/perfStats.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Browsertime changelog (we do [semantic versioning](https://semver.org))
|
|
2
2
|
|
|
3
|
+
## 25.3.0 - 2025-10-17
|
|
4
|
+
### Added
|
|
5
|
+
* Make it possible to strip cookie and auth headers in the HAR file for Firefox [#2329](https://github.com/sitespeedio/browsertime/pull/2329) and Chrome [#2330](https://github.com/sitespeedio/browsertime/pull/2330). Use `--cleanSensitiveHeaders` to remove a [couple of headers](https://github.com/sitespeedio/browsertime/blob/main/lib/support/har/index.js#L11-L24).
|
|
6
|
+
* Firefox 144 [#2331](https://github.com/sitespeedio/browsertime/pull/2331).
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
* Updated developer dependencies [#2326](https://github.com/sitespeedio/browsertime/pull/2326) and [#2327](https://github.com/sitespeedio/browsertime/pull/2327).
|
|
10
|
+
* Updated log dependency [#2328](https://github.com/sitespeedio/b23272327rowsertime/pull/2328)
|
|
11
|
+
|
|
12
|
+
## 25.2.0 - 2025-10-12
|
|
13
|
+
### Added
|
|
14
|
+
* Updated to Chrome/Chromedriver/Edge/Edgedriver 141, Firefox 143 [#2325](https://github.com/sitespeedio/browsertime/pull/2325) and [#2323](https://github.com/sitespeedio/browsertime/pull/2323).
|
|
15
|
+
* Updated webdriver and bidi-har [#2322](https://github.com/sitespeedio/browsertime/pull/2322).
|
|
16
|
+
* Added simpleperf and perfetto support for Android, thank you [Abhishek Nimalan](https://github.com/animalan) for PR [#2315](https://github.com/sitespeedio/browsertime/pull/2315).
|
|
17
|
+
|
|
3
18
|
## 25.1.0 - 2025-09-05
|
|
4
19
|
### Added
|
|
5
20
|
* Updated to Chrome/Chromedriver 140, Firefox 142 [#2318](https://github.com/sitespeedio/browsertime/pull/2318).
|
package/lib/android/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { promisify } from 'node:util';
|
|
2
|
-
import { mkdir as _mkdir, createWriteStream } from 'node:fs';
|
|
2
|
+
import { mkdir as _mkdir, createWriteStream, createReadStream } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { EOL as endOfLine } from 'node:os';
|
|
5
5
|
import { execa } from 'execa';
|
|
@@ -100,6 +100,11 @@ export class Android {
|
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
async _uploadFile(sourcePath, destinationPath) {
|
|
104
|
+
log.debug(`Pushing to ${destinationPath} from ${sourcePath}`);
|
|
105
|
+
return this.device.push(createReadStream(sourcePath), destinationPath);
|
|
106
|
+
}
|
|
107
|
+
|
|
103
108
|
async _downloadDir(sourcePath, destinationPath) {
|
|
104
109
|
const files = await this.device.readdir(sourcePath);
|
|
105
110
|
|
package/lib/chrome/har.js
CHANGED
|
@@ -2,7 +2,7 @@ import { getLogger } from '@sitespeed.io/log';
|
|
|
2
2
|
import { harFromMessages } from 'chrome-har';
|
|
3
3
|
import { logging } from 'selenium-webdriver';
|
|
4
4
|
const log = getLogger('browsertime.chrome');
|
|
5
|
-
import { addBrowser } from '../support/har/index.js';
|
|
5
|
+
import { addBrowser, cleanSensitiveHeaders } from '../support/har/index.js';
|
|
6
6
|
const { Type } = logging;
|
|
7
7
|
|
|
8
8
|
export async function getHar(
|
|
@@ -15,7 +15,8 @@ export async function getHar(
|
|
|
15
15
|
mobileEmulation,
|
|
16
16
|
androidClient,
|
|
17
17
|
chrome,
|
|
18
|
-
aliasAndUrl
|
|
18
|
+
aliasAndUrl,
|
|
19
|
+
cleanHeaders
|
|
19
20
|
) {
|
|
20
21
|
log.debug('Getting performance logs from Chrome');
|
|
21
22
|
|
|
@@ -34,6 +35,17 @@ export async function getHar(
|
|
|
34
35
|
|
|
35
36
|
const har = harFromMessages(messages);
|
|
36
37
|
|
|
38
|
+
if (cleanHeaders === true) {
|
|
39
|
+
for (const entry of har.log.entries ?? []) {
|
|
40
|
+
for (const header of entry.request?.headers ?? []) {
|
|
41
|
+
header.value = cleanSensitiveHeaders(header.name, header.value);
|
|
42
|
+
}
|
|
43
|
+
for (const header of entry.response?.headers ?? []) {
|
|
44
|
+
header.value = cleanSensitiveHeaders(header.name, header.value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
if (includeResponseBodies === 'html' || includeResponseBodies === 'all') {
|
|
38
50
|
await cdpClient.setResponseBodies(har);
|
|
39
51
|
}
|
|
@@ -227,7 +227,7 @@ export class Chromium {
|
|
|
227
227
|
const minLength = ${this.options.minLongTaskLength || 50};
|
|
228
228
|
const observer = new PerformanceObserver(list => {});
|
|
229
229
|
observer.observe({type: 'longtask', buffered: true});
|
|
230
|
-
const entries = observer.takeRecords();
|
|
230
|
+
const entries = observer.takeRecords();
|
|
231
231
|
const cleaned = [];
|
|
232
232
|
for (let event of entries) {
|
|
233
233
|
if (event.duration >= minLength) {
|
|
@@ -319,7 +319,8 @@ export class Chromium {
|
|
|
319
319
|
this.chrome.mobileEmulation,
|
|
320
320
|
this.android,
|
|
321
321
|
this.chrome,
|
|
322
|
-
this.aliasAndUrl
|
|
322
|
+
this.aliasAndUrl,
|
|
323
|
+
this.options.cleanSensitiveHeaders
|
|
323
324
|
)
|
|
324
325
|
);
|
|
325
326
|
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { getLogger } from '@sitespeed.io/log';
|
|
2
|
+
const log = getLogger('browsertime.command.perfetto');
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const { join } = path;
|
|
5
|
+
import { writeFile } from 'node:fs/promises';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { isAndroidConfigured, Android } from '../../../android/index.js';
|
|
8
|
+
const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
9
|
+
/**
|
|
10
|
+
* Manages the collection of perfetto traces on Android.
|
|
11
|
+
*
|
|
12
|
+
* @class
|
|
13
|
+
* @hideconstructor
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Default config that works for both chrome and firefox. It contains
|
|
17
|
+
// basic system information, but no track events. Those must be specified
|
|
18
|
+
// manually through perfetto.start(config).
|
|
19
|
+
const defaultConfig = `write_into_file: true
|
|
20
|
+
|
|
21
|
+
buffers: {
|
|
22
|
+
size_kb: 522240
|
|
23
|
+
fill_policy: DISCARD
|
|
24
|
+
}
|
|
25
|
+
buffers: {
|
|
26
|
+
size_kb: 2048
|
|
27
|
+
fill_policy: DISCARD
|
|
28
|
+
}
|
|
29
|
+
data_sources: {
|
|
30
|
+
config {
|
|
31
|
+
name: "android.packages_list"
|
|
32
|
+
target_buffer: 1
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
data_sources: {
|
|
36
|
+
config {
|
|
37
|
+
name: "linux.process_stats"
|
|
38
|
+
target_buffer: 1
|
|
39
|
+
process_stats_config {
|
|
40
|
+
scan_all_processes_on_start: true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
data_sources: {
|
|
45
|
+
config {
|
|
46
|
+
name: "linux.ftrace"
|
|
47
|
+
ftrace_config {
|
|
48
|
+
ftrace_events: "power/suspend_resume"
|
|
49
|
+
ftrace_events: "power/cpu_frequency"
|
|
50
|
+
ftrace_events: "power/cpu_idle"
|
|
51
|
+
ftrace_events: "regulator/regulator_set_voltage"
|
|
52
|
+
ftrace_events: "regulator/regulator_set_voltage_complete"
|
|
53
|
+
ftrace_events: "power/clock_enable"
|
|
54
|
+
ftrace_events: "power/clock_disable"
|
|
55
|
+
ftrace_events: "power/clock_set_rate"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
duration_ms: 60000`;
|
|
60
|
+
export class PerfettoTrace {
|
|
61
|
+
constructor(browser, index, storageManager, options) {
|
|
62
|
+
/**
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
this.browser = browser;
|
|
66
|
+
/**
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
this.storageManager = storageManager;
|
|
70
|
+
/**
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
this.options = options;
|
|
74
|
+
/**
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
this.index = index;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async uploadConfigFile(android, config) {
|
|
81
|
+
let destinationFilename = join(this.dataDir, `perfetto-config.txt`);
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await writeFile(destinationFilename, config);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(error);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return android._uploadFile(
|
|
90
|
+
destinationFilename,
|
|
91
|
+
'/sdcard/browsertime-perfetto-config.txt'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async downloadTrace() {
|
|
96
|
+
let destinationFilename = join(this.dataDir, `trace.perfetto`);
|
|
97
|
+
let deviceTraceFilename = '/data/misc/perfetto-traces/trace';
|
|
98
|
+
log.info(
|
|
99
|
+
`Downloading perfetto trace from ${deviceTraceFilename} to ${destinationFilename}`
|
|
100
|
+
);
|
|
101
|
+
return this.android._downloadFile(deviceTraceFilename, destinationFilename);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Begin Perfetto Trace Collection.
|
|
106
|
+
*
|
|
107
|
+
* @async
|
|
108
|
+
* @returns {Promise<void>} A promise that resolves when tracing is finished.
|
|
109
|
+
* @throws {Error} Throws an error if the configuration is not set for perfetto tracing.
|
|
110
|
+
*/
|
|
111
|
+
async start(config = defaultConfig) {
|
|
112
|
+
if (!isAndroidConfigured(this.options)) {
|
|
113
|
+
throw new Error('Perfetto tracing is only available on Android.');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.android = new Android(this.options);
|
|
117
|
+
|
|
118
|
+
// Create empty subdir for simpleperf data.
|
|
119
|
+
let dirname = `perfetto-${this.index}`;
|
|
120
|
+
let counter = 1;
|
|
121
|
+
|
|
122
|
+
while (true) {
|
|
123
|
+
log.info(`Checking if ${dirname} exists...`);
|
|
124
|
+
if (existsSync(join(this.storageManager.directory, dirname))) {
|
|
125
|
+
dirname = `perfetto-${this.index}.${counter}`;
|
|
126
|
+
counter++;
|
|
127
|
+
log.info(`Directory already exists.`);
|
|
128
|
+
} else {
|
|
129
|
+
this.dataDir = await this.storageManager.createSubDataDir(dirname);
|
|
130
|
+
log.info(`Creating subdir ${this.dataDir}.`);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
log.info('Finished creating directory.');
|
|
135
|
+
|
|
136
|
+
// Check if perfetto is enabled on this device.
|
|
137
|
+
const tracedEnabled = await this.android._runCommandAndGet(
|
|
138
|
+
`getprop persist.traced.enable`
|
|
139
|
+
);
|
|
140
|
+
if (tracedEnabled !== '1') {
|
|
141
|
+
log.info(`Disabling Perfetto: persist.traced.enable=${tracedEnabled}`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create the trace config on the device.
|
|
146
|
+
log.info('Creating perfetto config on device.');
|
|
147
|
+
await this.uploadConfigFile(this.android, config);
|
|
148
|
+
await delay(5000);
|
|
149
|
+
|
|
150
|
+
// Start perfetto trace and detach.
|
|
151
|
+
log.info('Starting perfetto tracing.');
|
|
152
|
+
const output = await this.android._runCommandAndGet(
|
|
153
|
+
'cat /sdcard/browsertime-perfetto-config.txt | perfetto -c - --txt --detach=browsertime -o /data/misc/perfetto-traces/trace'
|
|
154
|
+
);
|
|
155
|
+
log.info(`Perfetto starting output: ${output}`);
|
|
156
|
+
|
|
157
|
+
// Remove the config file.
|
|
158
|
+
await this.android.removeFileOnSdCard('browsertime-perfetto-config.txt');
|
|
159
|
+
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
if (output.includes('Connected to the Perfetto traced service')) {
|
|
162
|
+
this.running = true;
|
|
163
|
+
return resolve();
|
|
164
|
+
} else {
|
|
165
|
+
return reject();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Stop Perfetto Trace Collection.
|
|
172
|
+
*
|
|
173
|
+
* @async
|
|
174
|
+
* @returns {Promise<void>} A promise that resolves when tracing is finished and the perfetto
|
|
175
|
+
* trace has been collected.
|
|
176
|
+
* @throws {Error} Throws an error if the the perfetto session or trace was not found.
|
|
177
|
+
*/
|
|
178
|
+
async stop() {
|
|
179
|
+
if (!isAndroidConfigured(this.options)) {
|
|
180
|
+
throw new Error('Perfetto tracing is only available on Android.');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!this.running) {
|
|
184
|
+
throw new Error('Perfetto tracing was not started.');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Attach and stop perfetto trace.
|
|
188
|
+
log.info('Starting perfetto tracing.');
|
|
189
|
+
const output = await this.android._runCommandAndGet(
|
|
190
|
+
'perfetto --attach=browsertime --stop'
|
|
191
|
+
);
|
|
192
|
+
log.info(`Perfetto ending output: ${output}`);
|
|
193
|
+
|
|
194
|
+
let stopSuccessful = false;
|
|
195
|
+
|
|
196
|
+
if (output.includes('Trace written into the output file')) {
|
|
197
|
+
stopSuccessful = true;
|
|
198
|
+
} else {
|
|
199
|
+
const logcat = await this.android._runCommandAndGet(`logcat -d`);
|
|
200
|
+
if (/Tracing session \d+ ended, total sessions:0/.test(logcat)) {
|
|
201
|
+
log.info('Warning: Perfetto session ended prematurely.');
|
|
202
|
+
stopSuccessful = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.running = false;
|
|
206
|
+
|
|
207
|
+
if (!stopSuccessful) {
|
|
208
|
+
throw new Error('Perfetto process failed or ended suddenly.');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
log.info('Perfetto tracing finished.');
|
|
212
|
+
// Download perfetto trace.
|
|
213
|
+
return this.downloadTrace();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { getLogger } from '@sitespeed.io/log';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const { join } = path;
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import { existsSync, renameSync } from 'node:fs';
|
|
6
|
+
import { isAndroidConfigured } from '../../../android/index.js';
|
|
7
|
+
// const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
8
|
+
/**
|
|
9
|
+
* Manages the collection of perfetto traces on Android.
|
|
10
|
+
*
|
|
11
|
+
* @class
|
|
12
|
+
* @hideconstructor
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const log = getLogger('browsertime.command.simpleperf');
|
|
16
|
+
|
|
17
|
+
const defaultOptions =
|
|
18
|
+
'--call-graph fp --duration 240 -f 1000 --trace-offcpu -e cpu-clock';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Timeout a promise after ms. Use promise.race to compete
|
|
22
|
+
* about the timeout and the promise.
|
|
23
|
+
* @param {promise} promise - the promise to wait for
|
|
24
|
+
* @param {int} ms - how long in ms to wait for the promise to fininsh
|
|
25
|
+
* @param {string} errorMessage - the error message in the Error if we timeouts
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
async function timeout(promise, ms, errorMessage) {
|
|
29
|
+
let timer;
|
|
30
|
+
|
|
31
|
+
return Promise.race([
|
|
32
|
+
new Promise((resolve, reject) => {
|
|
33
|
+
timer = setTimeout(reject, ms, new Error(errorMessage));
|
|
34
|
+
return timer;
|
|
35
|
+
}),
|
|
36
|
+
promise.then(value => {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
return value;
|
|
39
|
+
})
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class SimplePerfProfiler {
|
|
44
|
+
constructor(browser, index, storageManager, options) {
|
|
45
|
+
/**
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
this.browser = browser;
|
|
49
|
+
/**
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
this.storageManager = storageManager;
|
|
53
|
+
/**
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
this.options = options;
|
|
57
|
+
/**
|
|
58
|
+
* @private
|
|
59
|
+
*/
|
|
60
|
+
this.index = index;
|
|
61
|
+
/**
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
this.running = false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Start Simpleperf profiling.
|
|
69
|
+
*
|
|
70
|
+
* @async
|
|
71
|
+
* @returns {Promise<void>} A promise that resolves when simpleperf has started profiling.
|
|
72
|
+
* @throws {Error} Throws an error if app_profiler.py fails to execute.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
async start(profilerOptions = defaultOptions, dirName = 'simpleperf') {
|
|
76
|
+
if (!isAndroidConfigured(this.options)) {
|
|
77
|
+
throw new Error('Simpleperf profiling is only available on Android.');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
log.info('Starting simpleperf profiler.');
|
|
81
|
+
|
|
82
|
+
// Create empty subdirectory for simpleperf data.
|
|
83
|
+
let dirname = `${dirName}-${this.index}`;
|
|
84
|
+
let counter = 0;
|
|
85
|
+
|
|
86
|
+
while (true) {
|
|
87
|
+
log.info(`Checking if ${dirname} exists...`);
|
|
88
|
+
|
|
89
|
+
if (existsSync(join(this.storageManager.directory, dirname))) {
|
|
90
|
+
dirname = `${dirName}-${this.index}.${counter}`;
|
|
91
|
+
counter++;
|
|
92
|
+
log.info(`Directory already exists.`);
|
|
93
|
+
} else {
|
|
94
|
+
this.dataDir = await this.storageManager.createSubDataDir(dirname);
|
|
95
|
+
log.info(`Creating subdir ${this.dataDir}.`);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Execute simpleperf.
|
|
101
|
+
const packageName =
|
|
102
|
+
this.options.browser === 'firefox'
|
|
103
|
+
? this.options.firefox?.android?.package
|
|
104
|
+
: this.options.chrome?.android?.package;
|
|
105
|
+
let ndkPath = this.options.androidNDK;
|
|
106
|
+
let cmd = `${ndkPath}/simpleperf/app_profiler.py`;
|
|
107
|
+
let args = [
|
|
108
|
+
'-p',
|
|
109
|
+
packageName,
|
|
110
|
+
'-r',
|
|
111
|
+
profilerOptions,
|
|
112
|
+
'--log',
|
|
113
|
+
'debug',
|
|
114
|
+
'-o',
|
|
115
|
+
join(this.dataDir, 'perf.data')
|
|
116
|
+
];
|
|
117
|
+
this.simpleperfProcess = execa(cmd, args);
|
|
118
|
+
|
|
119
|
+
// Waiting for simpleperf to start.
|
|
120
|
+
let simpleperfPromise = new Promise((resolve, reject) => {
|
|
121
|
+
let stderrStream = this.simpleperfProcess.stderr;
|
|
122
|
+
stderrStream.on('data', data => {
|
|
123
|
+
let dataStr = data.toString();
|
|
124
|
+
log.info(dataStr);
|
|
125
|
+
if (/command 'record' starts running/.test(dataStr)) {
|
|
126
|
+
this.running = true;
|
|
127
|
+
stderrStream.removeAllListeners('data');
|
|
128
|
+
return resolve();
|
|
129
|
+
}
|
|
130
|
+
if (/Failed to record profiling data./.test(dataStr)) {
|
|
131
|
+
this.running = false;
|
|
132
|
+
log.info(`Error starting simpleperf: ${dataStr}`);
|
|
133
|
+
throw new Error('Simpleperf failed to start.');
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
stderrStream.once('error', reject);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Set a 30s timeout for starting simpleperf.
|
|
140
|
+
return timeout(simpleperfPromise, 30_000, 'Simpleperf timed out.');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Stop Simpleperf profiling.
|
|
145
|
+
*
|
|
146
|
+
* @async
|
|
147
|
+
* @returns {Promise<void>} A promise that resolves when simpleperf has stopped profiling
|
|
148
|
+
* and collected profile data.
|
|
149
|
+
* @throws {Error} Throws an error if app_profiler.py fails to execute.
|
|
150
|
+
*/
|
|
151
|
+
async stop() {
|
|
152
|
+
if (!isAndroidConfigured(this.options)) {
|
|
153
|
+
throw new Error('Simpleperf profiling is only available on Android.');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!this.running) {
|
|
157
|
+
throw new Error('Simpleperf profiling was not started.');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
log.info('Stop simpleperf profiler.');
|
|
161
|
+
this.simpleperfProcess.kill('SIGINT');
|
|
162
|
+
|
|
163
|
+
// Return when "profiling is finished." is found, or an error.
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
165
|
+
let stderrStream = this.simpleperfProcess.stderr;
|
|
166
|
+
log.info('Reading stderr.');
|
|
167
|
+
stderrStream.on('data', data => {
|
|
168
|
+
const dataStr = data.toString();
|
|
169
|
+
log.info(dataStr);
|
|
170
|
+
if (/profiling is finished./.test(dataStr)) {
|
|
171
|
+
stderrStream.removeAllListeners('data');
|
|
172
|
+
// There is no way to specify the output of binary_cache, so manually move
|
|
173
|
+
// it into the data directory.
|
|
174
|
+
renameSync('binary_cache', join(this.dataDir, 'binary_cache'));
|
|
175
|
+
return resolve();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
stderrStream.once('error', reject);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -29,6 +29,8 @@ import { Navigation } from './command/navigation.js';
|
|
|
29
29
|
import { GeckoProfiler } from '../../firefox/geckoProfiler.js';
|
|
30
30
|
import { GeckoProfiler as GeckoProfilerCommand } from './command/geckoProfiler.js';
|
|
31
31
|
import { PerfStatsInterface } from './command/perfStats.js';
|
|
32
|
+
import { PerfettoTrace } from './command/perfetto.js';
|
|
33
|
+
import { SimplePerfProfiler } from './command/simpleperf.js';
|
|
32
34
|
/**
|
|
33
35
|
* Represents the set of commands available in a Browsertime script.
|
|
34
36
|
* @hideconstructor
|
|
@@ -65,6 +67,23 @@ export class Commands {
|
|
|
65
67
|
options
|
|
66
68
|
);
|
|
67
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Provides functionality to collect perfetto traces.
|
|
72
|
+
* @type {PerfettoTrace}
|
|
73
|
+
*/
|
|
74
|
+
this.perfetto = new PerfettoTrace(browser, index, storageManager, options);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Provides functionality to collect simpleperf profiles.
|
|
78
|
+
* @type {SimplePerfProfiler}
|
|
79
|
+
*/
|
|
80
|
+
this.simpleperf = new SimplePerfProfiler(
|
|
81
|
+
browser,
|
|
82
|
+
index,
|
|
83
|
+
storageManager,
|
|
84
|
+
options
|
|
85
|
+
);
|
|
86
|
+
|
|
68
87
|
/**
|
|
69
88
|
* Manages GeckoProfiler functionality to collect performance profiles.
|
|
70
89
|
* @type {GeckoProfiler}
|
package/lib/firefox/perfStats.js
CHANGED
|
@@ -6,15 +6,51 @@ export class PerfStats {
|
|
|
6
6
|
this.runner = runner;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
async start(
|
|
9
|
+
async start(features) {
|
|
10
10
|
const runner = this.runner;
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
// Parse features if it's a string
|
|
13
|
+
let featuresArray = features;
|
|
14
|
+
if (typeof features === 'string') {
|
|
15
|
+
featuresArray = features
|
|
16
|
+
.split(',')
|
|
17
|
+
.map(f => f.trim())
|
|
18
|
+
.filter(f => f.length > 0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line prettier/prettier
|
|
22
|
+
const allFeaturesMask = 0xFF_FF_FF_FF;
|
|
23
|
+
|
|
24
|
+
const script = `
|
|
25
|
+
if (typeof ChromeUtils.setPerfStatsFeatures === 'function') {
|
|
26
|
+
if (!${JSON.stringify(featuresArray)} || ${JSON.stringify(featuresArray)}.length === 0) {
|
|
27
|
+
ChromeUtils.enableAllPerfStatsFeatures();
|
|
28
|
+
} else {
|
|
29
|
+
ChromeUtils.setPerfStatsFeatures(${JSON.stringify(featuresArray)});
|
|
30
|
+
}
|
|
31
|
+
} else if (typeof ChromeUtils.setPerfStatsCollectionMask === 'function') {
|
|
32
|
+
ChromeUtils.setPerfStatsCollectionMask(${features || allFeaturesMask});
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error('PerfStats API not available in this Firefox version');
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
12
38
|
return runner.runPrivilegedScript(script, 'Start PerfStats Measurement');
|
|
13
39
|
}
|
|
14
40
|
|
|
15
41
|
async stop() {
|
|
16
42
|
const runner = this.runner;
|
|
17
|
-
|
|
43
|
+
|
|
44
|
+
const script = `
|
|
45
|
+
if (typeof ChromeUtils.setPerfStatsFeatures === 'function') {
|
|
46
|
+
ChromeUtils.setPerfStatsFeatures([]);
|
|
47
|
+
} else if (typeof ChromeUtils.setPerfStatsCollectionMask === 'function') {
|
|
48
|
+
ChromeUtils.setPerfStatsCollectionMask(0);
|
|
49
|
+
} else {
|
|
50
|
+
throw new Error('PerfStats API not available in this Firefox version');
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
18
54
|
return runner.runPrivilegedScript(script, 'Stop PerfStats Measurement');
|
|
19
55
|
}
|
|
20
56
|
|
|
@@ -3,7 +3,11 @@ import { promisify } from 'node:util';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getLogger } from '@sitespeed.io/log';
|
|
5
5
|
import { adapters } from 'ff-test-bidi-har-export';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getEmptyHAR,
|
|
8
|
+
mergeHars,
|
|
9
|
+
cleanSensitiveHeaders
|
|
10
|
+
} from '../../support/har/index.js';
|
|
7
11
|
import { loadUsbPowerProfiler } from '../../support/usbPower.js';
|
|
8
12
|
import { pathToFolder } from '../../support/pathToFolder.js';
|
|
9
13
|
import { isAndroidConfigured, Android } from '../../android/index.js';
|
|
@@ -63,7 +67,12 @@ export class Firefox {
|
|
|
63
67
|
browsingContextIds: [this.windowId],
|
|
64
68
|
debugLogs:
|
|
65
69
|
this.options.verbose >= 2 || this.firefoxConfig.enableBidiHarLog,
|
|
66
|
-
driver: runner.getDriver()
|
|
70
|
+
driver: runner.getDriver(),
|
|
71
|
+
headerValueFormatter: this.options.cleanSensitiveHeaders
|
|
72
|
+
? cleanSensitiveHeaders
|
|
73
|
+
: function (name, value) {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
67
76
|
});
|
|
68
77
|
}
|
|
69
78
|
|
|
@@ -177,7 +186,7 @@ export class Firefox {
|
|
|
177
186
|
|
|
178
187
|
if (this.firefoxConfig.perfStats) {
|
|
179
188
|
this.perfStats = new PerfStats(runner);
|
|
180
|
-
return this.perfStats.start(this.firefoxConfig.perfStatsParams
|
|
189
|
+
return this.perfStats.start(this.firefoxConfig.perfStatsParams?.features);
|
|
181
190
|
}
|
|
182
191
|
|
|
183
192
|
this.testStartTime = Date.now();
|
package/lib/support/cli.js
CHANGED
|
@@ -549,11 +549,10 @@ export function parseCommandLine() {
|
|
|
549
549
|
type: 'boolean',
|
|
550
550
|
group: 'firefox'
|
|
551
551
|
})
|
|
552
|
-
.option('firefox.perfStatsParams.
|
|
553
|
-
describe:
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
type: 'number',
|
|
552
|
+
.option('firefox.perfStatsParams.features', {
|
|
553
|
+
describe:
|
|
554
|
+
'Comma-separated list of PerfStats features to enable. If not provided, all features will be enabled.',
|
|
555
|
+
type: 'string',
|
|
557
556
|
group: 'firefox'
|
|
558
557
|
})
|
|
559
558
|
.option('firefox.collectMozLog', {
|
|
@@ -838,6 +837,18 @@ export function parseCommandLine() {
|
|
|
838
837
|
'Before a test start, verify that the device has a Internet connection by pinging 8.8.8.8 (or a configurable domain with --androidPingAddress)',
|
|
839
838
|
group: 'android'
|
|
840
839
|
})
|
|
840
|
+
.option('android.ndk', {
|
|
841
|
+
alias: 'androidNDK',
|
|
842
|
+
type: 'string',
|
|
843
|
+
describe: 'Path to the Android NDK (required for simpleperf profiling).',
|
|
844
|
+
group: 'android'
|
|
845
|
+
})
|
|
846
|
+
.option('android.perfettoTrace', {
|
|
847
|
+
alias: 'androidPerfettoTrace',
|
|
848
|
+
describe: 'Collect a perfetto trace with the given configuration.',
|
|
849
|
+
default: false,
|
|
850
|
+
group: 'android'
|
|
851
|
+
})
|
|
841
852
|
/** Process start time. Android-only for now. */
|
|
842
853
|
.option('processStartTime', {
|
|
843
854
|
type: 'boolean',
|
|
@@ -1206,6 +1217,11 @@ export function parseCommandLine() {
|
|
|
1206
1217
|
type: 'boolean',
|
|
1207
1218
|
describe: 'Pass --gzipHar to gzip the HAR file'
|
|
1208
1219
|
})
|
|
1220
|
+
.option('cleanSensitiveHeaders', {
|
|
1221
|
+
type: 'boolean',
|
|
1222
|
+
describe:
|
|
1223
|
+
'Pass --cleanSensitiveHeaders to remove sensitive headers in the HAR file'
|
|
1224
|
+
})
|
|
1209
1225
|
.option('config', {
|
|
1210
1226
|
describe:
|
|
1211
1227
|
'Path to JSON config file. You can also use a .browsertime.json file that will automatically be found by Browsertime using find-up.',
|
package/lib/support/har/index.js
CHANGED
|
@@ -8,6 +8,21 @@ const require = createRequire(import.meta.url);
|
|
|
8
8
|
const version = require('../../../package.json').version;
|
|
9
9
|
import { pick, isEmpty, getProperty } from '../../support/util.js';
|
|
10
10
|
|
|
11
|
+
const sensitiveHeaders = new Set([
|
|
12
|
+
'authorization',
|
|
13
|
+
'proxy-authorization',
|
|
14
|
+
'cookie',
|
|
15
|
+
'set-cookie',
|
|
16
|
+
'x-api-key',
|
|
17
|
+
'x-auth-token',
|
|
18
|
+
'x-access-token',
|
|
19
|
+
'x-client-secret',
|
|
20
|
+
'x-csrf-token',
|
|
21
|
+
'x-xsrf-token',
|
|
22
|
+
'x-amz-security-token',
|
|
23
|
+
'x-amz-signature'
|
|
24
|
+
]);
|
|
25
|
+
|
|
11
26
|
function generateUniquePageId(baseId, existingIdMap) {
|
|
12
27
|
let newId = baseId;
|
|
13
28
|
while (existingIdMap.has(newId)) {
|
|
@@ -385,3 +400,10 @@ export function addExtraFieldsToHar(totalResults, har, options) {
|
|
|
385
400
|
}
|
|
386
401
|
}
|
|
387
402
|
}
|
|
403
|
+
|
|
404
|
+
export function cleanSensitiveHeaders(name, value) {
|
|
405
|
+
if (sensitiveHeaders.has(name.toLowerCase())) {
|
|
406
|
+
return '[REMOVED]';
|
|
407
|
+
}
|
|
408
|
+
return value;
|
|
409
|
+
}
|
package/package.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browsertime",
|
|
3
3
|
"description": "Get performance metrics from your web page using Browsertime.",
|
|
4
|
-
"version": "25.
|
|
4
|
+
"version": "25.3.0",
|
|
5
5
|
"bin": "./bin/browsertime.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"types": "./types/scripting.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@devicefarmer/adbkit": "3.3.8",
|
|
10
|
-
"@sitespeed.io/chromedriver": "
|
|
11
|
-
"@sitespeed.io/edgedriver": "
|
|
10
|
+
"@sitespeed.io/chromedriver": "141.0.7390-76",
|
|
11
|
+
"@sitespeed.io/edgedriver": "141.0.3537-71",
|
|
12
12
|
"@sitespeed.io/geckodriver": "0.36.0",
|
|
13
|
-
"@sitespeed.io/log": "0.
|
|
13
|
+
"@sitespeed.io/log": "1.0.0",
|
|
14
14
|
"@sitespeed.io/throttle": "5.0.1",
|
|
15
15
|
"@sitespeed.io/tracium": "0.3.3",
|
|
16
|
-
"chrome-har": "1.
|
|
16
|
+
"chrome-har": "1.1.1",
|
|
17
17
|
"chrome-remote-interface": "0.33.3",
|
|
18
18
|
"execa": "9.6.0",
|
|
19
19
|
"fast-stats": "0.0.7",
|
|
20
|
-
"ff-test-bidi-har-export": "0.0.
|
|
20
|
+
"ff-test-bidi-har-export": "0.0.18",
|
|
21
21
|
"lodash.merge": "4.6.2",
|
|
22
|
-
"selenium-webdriver": "4.
|
|
22
|
+
"selenium-webdriver": "4.36.0",
|
|
23
23
|
"yargs": "18.0.0"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
@@ -31,18 +31,18 @@
|
|
|
31
31
|
"usb-power-profiling": "1.6.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/selenium-webdriver": "4.1
|
|
35
|
-
"ava": "6.4.
|
|
34
|
+
"@types/selenium-webdriver": "4.35.1",
|
|
35
|
+
"ava": "6.4.1",
|
|
36
36
|
"clean-jsdoc-theme": "4.3.0",
|
|
37
|
-
"eslint": "9.
|
|
38
|
-
"eslint-config-prettier": "10.1.
|
|
39
|
-
"eslint-plugin-prettier": "5.5.
|
|
40
|
-
"eslint-plugin-unicorn": "
|
|
41
|
-
"jsdoc": "4.0.
|
|
37
|
+
"eslint": "9.37.0",
|
|
38
|
+
"eslint-config-prettier": "10.1.8",
|
|
39
|
+
"eslint-plugin-prettier": "5.5.4",
|
|
40
|
+
"eslint-plugin-unicorn": "61.0.2",
|
|
41
|
+
"jsdoc": "4.0.5",
|
|
42
42
|
"prettier": "3.6.2",
|
|
43
|
-
"serve": "14.2.
|
|
43
|
+
"serve": "14.2.5",
|
|
44
44
|
"serve-handler": "6.1.6",
|
|
45
|
-
"typescript": "5.
|
|
45
|
+
"typescript": "5.9.3"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|
|
48
48
|
"node": ">=20.0.0"
|
package/types/android/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export class Android {
|
|
|
15
15
|
_runAsRootAndGet(command: any): Promise<any>;
|
|
16
16
|
_runAsRoot(command: any): Promise<boolean>;
|
|
17
17
|
_downloadFile(sourcePath: any, destinationPath: any): Promise<any>;
|
|
18
|
+
_uploadFile(sourcePath: any, destinationPath: any): Promise<any>;
|
|
18
19
|
_downloadDir(sourcePath: any, destinationPath: any): Promise<void>;
|
|
19
20
|
getFullPathOnSdCard(path: any): string;
|
|
20
21
|
mkDirOnSdCard(dirName: any): Promise<any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/android/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/android/index.js"],"names":[],"mappings":"AAqlBA,2DAWC;AAjlBD;IACE,0BAwBC;IAfC,YAAgC;IAEhC,QAIC;IACD,UAAgC;IAGhC,6BAA6B;IAC7B,yBAA2B;IAC3B,eAAgC;IAKlC,uBAgBC;IALC,YAA4C;IAG1C,YAAoE;IAIxE,wCAEC;IAED,8CAIC;IAED,6CAIC;IAED,2CAUC;IAED,mEAYC;IAED,iEAGC;IAED,mEAcC;IAED,uCAEC;IAED,0CAGC;IAED,4CAMC;IAED,4CAMC;IAED,uBAGC;IAED,kCAOC;IAED;;;;;;;OAuBC;IAED,2CAKC;IAED,8BAKC;IAED,oKAuBC;IAED,2BAIC;IAED,qCAGC;IAED,iCAGC;IAED,wBASC;IAED,2CAOC;IAED,gCAEC;IAED,0BAIC;IAED,iCAGC;IAED,8CAIC;IAED,4BAEC;IAED,sCAYC;IAED,mDAsBC;IAED,gDA+CC;IAED;;;;;;;OAqDC;IAED,4DAQC;IAED,mCAcC;IAED,kCAQC;IAED,iCAIC;IAED;;;;OAGC;IAED;;;OAEC;IAED,6GAaC;CACF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class PerfettoTrace {
|
|
2
|
+
constructor(browser: any, index: any, storageManager: any, options: any);
|
|
3
|
+
/**
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
private browser;
|
|
7
|
+
/**
|
|
8
|
+
* @private
|
|
9
|
+
*/
|
|
10
|
+
private storageManager;
|
|
11
|
+
/**
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
14
|
+
private options;
|
|
15
|
+
/**
|
|
16
|
+
* @private
|
|
17
|
+
*/
|
|
18
|
+
private index;
|
|
19
|
+
uploadConfigFile(android: any, config: any): Promise<any>;
|
|
20
|
+
downloadTrace(): Promise<any>;
|
|
21
|
+
/**
|
|
22
|
+
* Begin Perfetto Trace Collection.
|
|
23
|
+
*
|
|
24
|
+
* @async
|
|
25
|
+
* @returns {Promise<void>} A promise that resolves when tracing is finished.
|
|
26
|
+
* @throws {Error} Throws an error if the configuration is not set for perfetto tracing.
|
|
27
|
+
*/
|
|
28
|
+
start(config?: string): Promise<void>;
|
|
29
|
+
android: Android;
|
|
30
|
+
dataDir: any;
|
|
31
|
+
running: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Stop Perfetto Trace Collection.
|
|
34
|
+
*
|
|
35
|
+
* @async
|
|
36
|
+
* @returns {Promise<void>} A promise that resolves when tracing is finished and the perfetto
|
|
37
|
+
* trace has been collected.
|
|
38
|
+
* @throws {Error} Throws an error if the the perfetto session or trace was not found.
|
|
39
|
+
*/
|
|
40
|
+
stop(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
import { Android } from '../../../android/index.js';
|
|
43
|
+
//# sourceMappingURL=perfetto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perfetto.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/perfetto.js"],"names":[],"mappings":"AA2DA;IACE,yEAiBC;IAhBC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,uBAAoC;IACpC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,cAAkB;IAGpB,0DAaC;IAED,8BAOC;IAED;;;;;;OAMG;IACH,wBAHa,OAAO,CAAC,IAAI,CAAC,CA4DzB;IApDC,iBAAwC;IAapC,aAAkE;IAiClE,iBAAmB;IAQzB;;;;;;;OAOG;IACH,QAJa,OAAO,CAAC,IAAI,CAAC,CAwCzB;CACF;wBAhN4C,2BAA2B"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class SimplePerfProfiler {
|
|
2
|
+
constructor(browser: any, index: any, storageManager: any, options: any);
|
|
3
|
+
/**
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
private browser;
|
|
7
|
+
/**
|
|
8
|
+
* @private
|
|
9
|
+
*/
|
|
10
|
+
private storageManager;
|
|
11
|
+
/**
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
14
|
+
private options;
|
|
15
|
+
/**
|
|
16
|
+
* @private
|
|
17
|
+
*/
|
|
18
|
+
private index;
|
|
19
|
+
/**
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
private running;
|
|
23
|
+
/**
|
|
24
|
+
* Start Simpleperf profiling.
|
|
25
|
+
*
|
|
26
|
+
* @async
|
|
27
|
+
* @returns {Promise<void>} A promise that resolves when simpleperf has started profiling.
|
|
28
|
+
* @throws {Error} Throws an error if app_profiler.py fails to execute.
|
|
29
|
+
*/
|
|
30
|
+
start(profilerOptions?: string, dirName?: string): Promise<void>;
|
|
31
|
+
dataDir: any;
|
|
32
|
+
simpleperfProcess: import("execa").ResultPromise<{}>;
|
|
33
|
+
/**
|
|
34
|
+
* Stop Simpleperf profiling.
|
|
35
|
+
*
|
|
36
|
+
* @async
|
|
37
|
+
* @returns {Promise<void>} A promise that resolves when simpleperf has stopped profiling
|
|
38
|
+
* and collected profile data.
|
|
39
|
+
* @throws {Error} Throws an error if app_profiler.py fails to execute.
|
|
40
|
+
*/
|
|
41
|
+
stop(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=simpleperf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simpleperf.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/simpleperf.js"],"names":[],"mappings":"AA0CA;IACE,yEAqBC;IApBC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,uBAAoC;IACpC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,cAAkB;IAClB;;OAEG;IACH,gBAAoB;IAGtB;;;;;;OAMG;IAEH,mDAJa,OAAO,CAAC,IAAI,CAAC,CAsEzB;IA/CK,aAAkE;IAuBtE,qDAAyC;IA0B3C;;;;;;;OAOG;IACH,QAJa,OAAO,CAAC,IAAI,CAAC,CAiCzB;CACF"}
|
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export class Commands {
|
|
6
6
|
constructor(browser: any, engineDelegate: any, index: any, result: any, storageManager: any, pageCompleteCheck: any, context: any, videos: any, screenshotManager: any, scriptsByCategory: any, asyncScriptsByCategory: any, postURLScripts: any, options: any);
|
|
7
|
+
/**
|
|
8
|
+
* Provides functionality to collect perfetto traces.
|
|
9
|
+
* @type {PerfettoTrace}
|
|
10
|
+
*/
|
|
11
|
+
perfetto: PerfettoTrace;
|
|
12
|
+
/**
|
|
13
|
+
* Provides functionality to collect simpleperf profiles.
|
|
14
|
+
* @type {SimplePerfProfiler}
|
|
15
|
+
*/
|
|
16
|
+
simpleperf: SimplePerfProfiler;
|
|
7
17
|
profiler: GeckoProfilerCommand;
|
|
8
18
|
perfStats: PerfStatsInterface;
|
|
9
19
|
/**
|
|
@@ -144,6 +154,8 @@ export class Commands {
|
|
|
144
154
|
*/
|
|
145
155
|
element: Element;
|
|
146
156
|
}
|
|
157
|
+
import { PerfettoTrace } from './command/perfetto.js';
|
|
158
|
+
import { SimplePerfProfiler } from './command/simpleperf.js';
|
|
147
159
|
import { GeckoProfiler as GeckoProfilerCommand } from './command/geckoProfiler.js';
|
|
148
160
|
import { PerfStatsInterface } from './command/perfStats.js';
|
|
149
161
|
import { ChromeTrace } from './command/chromeTrace.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../lib/core/engine/commands.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../lib/core/engine/commands.js"],"names":[],"mappings":"AAiCA;;;GAGG;AACH;IACE,gQAyQC;IA1OC;;;OAGG;IACH,UAFU,aAAa,CAEmD;IAE1E;;;OAGG;IACH,YAFU,kBAAkB,CAO3B;IAQD,+BAMC;IAOD,8BAA0B;IAO1B;;;OAGG;IACH,OAFU,WAAW,CAE+C;IAEpE;;;OAGG;IACH,OAFU,KAAK,CAEmC;IAElD;;;OAGG;IACH,QAFU,MAAM,CAE0B;IAE1C;;;OAGG;IACH,SAFU,OAAO,CAEkB;IAEnC;;;OAGG;IACH,MAFU,IAAI,CAEkC;IAEhD;;;OAGG;IACH,SAFU,OAAO,CAEK;IAEtB;;;;;;;;OAQG;IACH,mBAA+C;IAE/C;;;OAGG;IACH,YAFU,UAAU,CAEwC;IAE5D;;;;;OAKG;IACH,gBAAyC;IAEzC;;;;;OAKG;IACH,wBAAmD;IAEnD;;;OAGG;IACH,IAFU,UAAU,CAEgC;IAEpD;;;OAGG;IACH,QAFU,MAAM,CAMf;IAED;;;OAGG;IACH,KAFU,GAAG,CAEc;IAE3B;;;OAGG;IACH,WAFU,SAAS,CAEoB;IAEvC;;;OAGG;IACH,OAFU,KAAK,CAEsC;IAErD;;;OAGG;IACH,MAFU,IAAI,CAEQ;IAEtB;;;OAGG;IACH,YAFU,UAAU,CAE+C;IAEnE;;;OAGG;IACH,KAFU,8BAA8B,CAE1B;IAEd;;;;OAIG;IACH,MAFU,IAAI,CAEuC;IAErD;;;OAGG;IACH,SAFU,cAAc,CAEkB;IAE1C;;;;OAIG;IACH,OAFW,KAAK,CAEwB;IAExC;;;OAGG;IACH,WA0BC;IAED;;;OAGG;IACH,QAFU,MAAM,CAEiB;IAEjC;;;;OAIG;IACH,QAHU,OAAO,CAGiB;IAElC;;;OAGG;IACH,SAFU,OAAO,CAEkB;CAEtC;8BAjR6B,uBAAuB;mCAClB,yBAAyB;sDAHN,4BAA4B;mCAC/C,wBAAwB;4BAZ/B,0BAA0B;sBAhBhC,oBAAoB;uBAwBnB,qBAAqB;wBAzBpB,sBAAsB;qBAGzB,mBAAmB;wBAChB,sBAAsB;2BAsBnB,yBAAyB;2BArBzB,yBAAyB;uBAC7B,qBAAqB;oBAExB,kBAAkB;mCAGH,wBAAwB;sBAFrC,oBAAoB;qBACrB,mBAAmB;2BAHb,yBAAyB;+CASL,qCAAqC;qBAF/D,mBAAmB;+BACT,sBAAsB;sBAF/B,oBAAoB;uBADnB,qBAAqB;wBAbpB,sBAAsB;wBAGtB,sBAAsB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"perfStats.d.ts","sourceRoot":"","sources":["../../lib/firefox/perfStats.js"],"names":[],"mappings":"AAGA;IACE,yBAEC;IADC,YAAoB;IAGtB,
|
|
1
|
+
{"version":3,"file":"perfStats.d.ts","sourceRoot":"","sources":["../../lib/firefox/perfStats.js"],"names":[],"mappings":"AAGA;IACE,yBAEC;IADC,YAAoB;IAGtB,mCA8BC;IAED,qBAcC;IAED,uBAuCC;CACF"}
|