browsertime 16.16.0 → 17.0.0-beta.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 +52 -0
- package/bin/browsertime.js +43 -43
- package/bin/browsertimeWebPageReplay.js +10 -9
- package/browserscripts/pageinfo/generator.js +1 -1
- package/index.js +14 -13
- package/lib/android/gnirehtet.js +14 -15
- package/lib/android/index.js +92 -89
- package/lib/android/root.js +6 -9
- package/lib/chrome/chromeDevtoolsProtocol.js +46 -45
- package/lib/chrome/har.js +12 -13
- package/lib/chrome/longTaskMetrics.js +3 -5
- package/lib/chrome/parseCpuTrace.js +13 -10
- package/lib/chrome/settings/chromeAndroidOptions.js +1 -3
- package/lib/chrome/settings/chromeDesktopOptions.js +1 -3
- package/lib/chrome/settings/traceCategories.js +1 -3
- package/lib/chrome/traceCategoriesParser.js +35 -36
- package/lib/chrome/webdriver/builder.js +18 -26
- package/lib/chrome/webdriver/chromium.js +56 -46
- package/lib/chrome/webdriver/setupChromiumOptions.js +32 -27
- package/lib/connectivity/humble.js +13 -14
- package/lib/connectivity/index.js +74 -77
- package/lib/connectivity/trafficShapeParser.js +31 -35
- package/lib/core/engine/collector.js +25 -22
- package/lib/core/engine/command/addText.js +24 -27
- package/lib/core/engine/command/android.js +7 -11
- package/lib/core/engine/command/cache.js +3 -4
- package/lib/core/engine/command/chromeDevToolsProtocol.js +20 -20
- package/lib/core/engine/command/click.js +48 -49
- package/lib/core/engine/command/debug.js +3 -4
- package/lib/core/engine/command/javaScript.js +18 -19
- package/lib/core/engine/command/measure.js +31 -37
- package/lib/core/engine/command/meta.js +1 -4
- package/lib/core/engine/command/mouse/clickAndHold.js +36 -39
- package/lib/core/engine/command/mouse/contextClick.js +17 -18
- package/lib/core/engine/command/mouse/doubleClick.js +17 -19
- package/lib/core/engine/command/mouse/index.js +5 -9
- package/lib/core/engine/command/mouse/mouseMove.js +19 -23
- package/lib/core/engine/command/mouse/singleClick.js +15 -19
- package/lib/core/engine/command/navigation.js +12 -14
- package/lib/core/engine/command/screenshot.js +1 -4
- package/lib/core/engine/command/scroll.js +4 -7
- package/lib/core/engine/command/select.js +26 -26
- package/lib/core/engine/command/set.js +25 -23
- package/lib/core/engine/command/stopWatch.js +5 -8
- package/lib/core/engine/command/switch.js +18 -20
- package/lib/core/engine/command/util/lcpHighlightScript.js +1 -2
- package/lib/core/engine/command/util/lsHighlightScript.js +1 -2
- package/lib/core/engine/command/wait.js +18 -18
- package/lib/core/engine/index.js +81 -71
- package/lib/core/engine/iteration.js +93 -79
- package/lib/core/engine/run.js +5 -9
- package/lib/core/pageCompleteChecks/defaultPageCompleteCheck.js +1 -2
- package/lib/core/pageCompleteChecks/pageCompleteCheckByInactivity.js +1 -3
- package/lib/core/pageCompleteChecks/spaInactivity.js +1 -3
- package/lib/core/seleniumRunner.js +92 -100
- package/lib/core/webdriver/index.js +24 -21
- package/lib/edge/webdriver/builder.js +21 -26
- package/lib/extensionserver/httpServer.js +17 -21
- package/lib/extensionserver/index.js +8 -12
- package/lib/extensionserver/setup.js +12 -12
- package/lib/firefox/geckoProfiler.js +27 -21
- package/lib/firefox/getHAR.js +7 -8
- package/lib/firefox/memoryReport.js +9 -11
- package/lib/firefox/perfStats.js +5 -7
- package/lib/firefox/settings/disableSafeBrowsingPreferences.js +1 -3
- package/lib/firefox/settings/disableTrackingProtectionPreferences.js +1 -3
- package/lib/firefox/settings/firefoxPreferences.js +2 -4
- package/lib/firefox/settings/geckoProfilerDefaults.js +5 -9
- package/lib/firefox/webdriver/builder.js +66 -63
- package/lib/firefox/webdriver/firefox.js +43 -48
- package/lib/safari/webdriver/builder.js +4 -5
- package/lib/safari/webdriver/safari.js +4 -8
- package/lib/screenshot/defaults.js +1 -3
- package/lib/screenshot/index.js +24 -21
- package/lib/support/browserScript.js +65 -56
- package/lib/support/cli.js +123 -85
- package/lib/support/dns.js +6 -7
- package/lib/support/engineUtils.js +85 -38
- package/lib/support/errors.js +4 -13
- package/lib/support/fileUtil.js +81 -68
- package/lib/support/filters.js +15 -20
- package/lib/support/getViewPort.js +6 -7
- package/lib/support/har/index.js +208 -216
- package/lib/support/images/index.js +54 -26
- package/lib/support/logging.js +20 -17
- package/lib/support/pathToFolder.js +18 -23
- package/lib/support/preURL.js +5 -6
- package/lib/support/processes.js +4 -6
- package/lib/support/setResourceTimingBufferSize.js +2 -4
- package/lib/support/statistics.js +58 -56
- package/lib/support/stop.js +7 -8
- package/lib/support/storageManager.js +44 -44
- package/lib/support/tcpdump.js +22 -16
- package/lib/support/userTiming.js +7 -13
- package/lib/support/util.js +204 -224
- package/lib/support/xvfb.js +11 -13
- package/lib/video/defaults.js +6 -10
- package/lib/video/postprocessing/finetune/addTextToVideo.js +15 -15
- package/lib/video/postprocessing/finetune/convertFps.js +14 -8
- package/lib/video/postprocessing/finetune/getFont.js +7 -9
- package/lib/video/postprocessing/finetune/getTimingMetrics.js +19 -18
- package/lib/video/postprocessing/finetune/index.js +40 -36
- package/lib/video/postprocessing/finetune/removeOrange.js +10 -11
- package/lib/video/postprocessing/visualmetrics/extraMetrics.js +17 -13
- package/lib/video/postprocessing/visualmetrics/getVideoMetrics.js +12 -13
- package/lib/video/postprocessing/visualmetrics/visualMetrics.js +117 -128
- package/lib/video/screenRecording/android/recorder.js +12 -17
- package/lib/video/screenRecording/desktop/convert.js +13 -11
- package/lib/video/screenRecording/desktop/desktopRecorder.js +29 -24
- package/lib/video/screenRecording/desktop/ffmpegRecorder.js +60 -63
- package/lib/video/screenRecording/desktop/osx/getSPDisplaysDataType.js +4 -6
- package/lib/video/screenRecording/desktop/osx/getScreen.js +9 -10
- package/lib/video/screenRecording/firefox/firefoxWindowRecorder.js +54 -54
- package/lib/video/screenRecording/ios/convertToMp4.js +10 -11
- package/lib/video/screenRecording/ios/iosRecorder.js +23 -21
- package/lib/video/screenRecording/iosSimulator/convertToMp4.js +10 -11
- package/lib/video/screenRecording/iosSimulator/recorder.js +10 -9
- package/lib/video/screenRecording/recorder.js +8 -9
- package/lib/video/screenRecording/setOrangeBackground.js +5 -6
- package/lib/video/video.js +28 -34
- package/package.json +19 -17
|
@@ -1,26 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const log =
|
|
6
|
-
const urlParser = require('url');
|
|
1
|
+
import { parse } from 'node:url';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import isEmpty from 'lodash.isempty';
|
|
4
|
+
import intel from 'intel';
|
|
5
|
+
const log = intel.getLogger('browsertime');
|
|
7
6
|
|
|
8
7
|
function toSafeKey(key) {
|
|
9
8
|
// U+2013 : EN DASH – as used on https://en.wikipedia.org/wiki/2019–20_coronavirus_pandemic
|
|
10
|
-
return key.replace(/[
|
|
9
|
+
return key.replace(/[ %&()+,./:?|~–]|%7C/g, '-');
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
export default function (url, options) {
|
|
14
13
|
if (options.useSameDir) {
|
|
15
14
|
return '';
|
|
16
15
|
} else {
|
|
17
16
|
const useHash = options.useHash;
|
|
18
|
-
const parsedUrl =
|
|
17
|
+
const parsedUrl = parse(decodeURIComponent(url));
|
|
19
18
|
|
|
20
19
|
const pathSegments = [];
|
|
21
20
|
const urlSegments = [];
|
|
22
|
-
pathSegments.push('pages');
|
|
23
|
-
pathSegments.push(parsedUrl.hostname.split('.').join('_'));
|
|
21
|
+
pathSegments.push('pages', parsedUrl.hostname.split('.').join('_'));
|
|
24
22
|
|
|
25
23
|
if (options.urlMetaData && options.urlMetaData[url]) {
|
|
26
24
|
pathSegments.push(options.urlMetaData[url]);
|
|
@@ -30,14 +28,14 @@ module.exports = function (url, options) {
|
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
if (useHash && !isEmpty(parsedUrl.hash)) {
|
|
33
|
-
const md5 =
|
|
34
|
-
hash = md5.update(parsedUrl.hash).digest('hex').
|
|
31
|
+
const md5 = createHash('md5'),
|
|
32
|
+
hash = md5.update(parsedUrl.hash).digest('hex').slice(0, 8);
|
|
35
33
|
urlSegments.push('hash-' + hash);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
if (!isEmpty(parsedUrl.search)) {
|
|
39
|
-
const md5 =
|
|
40
|
-
hash = md5.update(parsedUrl.search).digest('hex').
|
|
37
|
+
const md5 = createHash('md5'),
|
|
38
|
+
hash = md5.update(parsedUrl.search).digest('hex').slice(0, 8);
|
|
41
39
|
urlSegments.push('query-' + hash);
|
|
42
40
|
}
|
|
43
41
|
|
|
@@ -50,7 +48,7 @@ module.exports = function (url, options) {
|
|
|
50
48
|
log.info(
|
|
51
49
|
`The URL ${url} hit the 255 character limit used when stored on disk, you may want to give your URL an alias to make sure it will not collide with other URLs.`
|
|
52
50
|
);
|
|
53
|
-
pathSegments.push(folder.
|
|
51
|
+
pathSegments.push(folder.slice(0, 254));
|
|
54
52
|
} else {
|
|
55
53
|
pathSegments.push(folder);
|
|
56
54
|
}
|
|
@@ -59,15 +57,12 @@ module.exports = function (url, options) {
|
|
|
59
57
|
|
|
60
58
|
pathSegments.push('data');
|
|
61
59
|
|
|
62
|
-
|
|
60
|
+
for (const [index, segment] of pathSegments.entries()) {
|
|
63
61
|
if (segment) {
|
|
64
|
-
pathSegments[index] = segment.replace(
|
|
65
|
-
/[^-a-z0-9_.\u0621-\u064A]/gi,
|
|
66
|
-
'-'
|
|
67
|
-
);
|
|
62
|
+
pathSegments[index] = segment.replace(/[^\w.\u0621-\u064A-]/gi, '-');
|
|
68
63
|
}
|
|
69
|
-
}
|
|
64
|
+
}
|
|
70
65
|
|
|
71
66
|
return pathSegments.join('/').concat('/');
|
|
72
67
|
}
|
|
73
|
-
}
|
|
68
|
+
}
|
package/lib/support/preURL.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const log = require('intel').getLogger('browsertime');
|
|
1
|
+
import intel from 'intel';
|
|
2
|
+
const log = intel.getLogger('browsertime');
|
|
4
3
|
const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
export default async function (browser, options) {
|
|
7
6
|
log.info('Accessing preURL %s', options.preURL);
|
|
8
7
|
await browser.loadAndWait(options.preURL);
|
|
9
8
|
if (!options.preURLDisableWhiteBackground) {
|
|
@@ -13,5 +12,5 @@ module.exports = async function (browser, options) {
|
|
|
13
12
|
);
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
return delay(options.preURLDelay
|
|
17
|
-
}
|
|
15
|
+
return delay(options.preURLDelay ?? 1500);
|
|
16
|
+
}
|
package/lib/support/processes.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
module.exports = async function () {
|
|
5
|
-
const { stdout } = await execa.command('ps aux | wc -l', { shell: true });
|
|
1
|
+
import { execaCommand as command } from 'execa';
|
|
2
|
+
export default async function () {
|
|
3
|
+
const { stdout } = await command('ps aux | wc -l', { shell: true });
|
|
6
4
|
return stdout;
|
|
7
|
-
}
|
|
5
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = async function (startURL, driver, size) {
|
|
1
|
+
export default async function (startURL, driver, size) {
|
|
4
2
|
await driver.get(startURL);
|
|
5
3
|
await driver.executeScript(
|
|
6
4
|
`window.performance.setResourceTimingBufferSize(${size});`
|
|
7
5
|
);
|
|
8
|
-
}
|
|
6
|
+
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const get = require('lodash.get');
|
|
7
|
-
const set = require('lodash.set');
|
|
8
|
-
const Stats = require('fast-stats').Stats;
|
|
9
|
-
const log = require('intel').getLogger('browsertime');
|
|
1
|
+
import get from 'lodash.get';
|
|
2
|
+
import set from 'lodash.set';
|
|
3
|
+
import { Stats } from 'fast-stats';
|
|
4
|
+
import intel from 'intel';
|
|
5
|
+
const log = intel.getLogger('browsertime');
|
|
10
6
|
|
|
11
7
|
function validateType(value, type, message) {
|
|
12
8
|
if (typeof value !== type) {
|
|
@@ -24,7 +20,17 @@ function percentileName(percentile) {
|
|
|
24
20
|
}
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
function isNumeric(n) {
|
|
24
|
+
return !Number.isNaN(Number.parseFloat(n)) && Number.isFinite(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function addNumber(target, key, value) {
|
|
28
|
+
let stats = get(target, key, new Stats());
|
|
29
|
+
stats.push(value);
|
|
30
|
+
set(target, key, stats);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class Statistics {
|
|
28
34
|
constructor() {
|
|
29
35
|
this.data = {};
|
|
30
36
|
}
|
|
@@ -32,7 +38,7 @@ class Statistics {
|
|
|
32
38
|
add(key, value) {
|
|
33
39
|
validateType(key, 'string', 'key (' + key + ') must be a string');
|
|
34
40
|
validateType(value, 'number', 'value (' + value + ') must be a number');
|
|
35
|
-
if (!isFinite(value)) {
|
|
41
|
+
if (!Number.isFinite(value)) {
|
|
36
42
|
throw new RangeError("value can't be infinite");
|
|
37
43
|
}
|
|
38
44
|
|
|
@@ -43,28 +49,18 @@ class Statistics {
|
|
|
43
49
|
addAll(data) {
|
|
44
50
|
let samples = Array.isArray(data) ? data : [data];
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
Object.keys(sample)
|
|
52
|
+
for (const sample of samples) {
|
|
53
|
+
for (const key of Object.keys(sample)) {
|
|
48
54
|
let value = sample[key];
|
|
49
55
|
// TODO check type of value, to see if it should be included. Possibly via optional transform function
|
|
50
56
|
this.add(key, value);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
53
59
|
|
|
54
60
|
return this;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
addDeep(data, tranformFunction) {
|
|
58
|
-
function isNumeric(n) {
|
|
59
|
-
return !isNaN(parseFloat(n)) && isFinite(n);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function addNumber(target, key, value) {
|
|
63
|
-
let stats = get(target, key, new Stats());
|
|
64
|
-
stats.push(value);
|
|
65
|
-
set(target, key, stats);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
64
|
function addRecursive(target, keyPrefix, value) {
|
|
69
65
|
if (tranformFunction && keyPrefix.length > 0) {
|
|
70
66
|
value = tranformFunction(keyPrefix, value);
|
|
@@ -72,43 +68,48 @@ class Statistics {
|
|
|
72
68
|
const valueType = typeof value;
|
|
73
69
|
|
|
74
70
|
switch (valueType) {
|
|
75
|
-
case 'number':
|
|
71
|
+
case 'number': {
|
|
76
72
|
{
|
|
77
73
|
addNumber(target, keyPrefix, value);
|
|
78
74
|
}
|
|
79
75
|
break;
|
|
76
|
+
}
|
|
80
77
|
|
|
81
|
-
case 'object':
|
|
78
|
+
case 'object': {
|
|
82
79
|
{
|
|
83
80
|
if (value === null) {
|
|
84
81
|
break;
|
|
85
82
|
}
|
|
86
|
-
Object.keys(value)
|
|
87
|
-
const path = keyPrefix
|
|
83
|
+
for (const key of Object.keys(value)) {
|
|
84
|
+
const path = [...keyPrefix, key];
|
|
88
85
|
addRecursive(target, path, value[key]);
|
|
89
|
-
}
|
|
86
|
+
}
|
|
90
87
|
}
|
|
91
88
|
break;
|
|
89
|
+
}
|
|
92
90
|
|
|
93
|
-
case 'string':
|
|
91
|
+
case 'string': {
|
|
94
92
|
{
|
|
95
93
|
if (isNumeric(value)) {
|
|
96
|
-
addNumber(target, keyPrefix, parseFloat(value));
|
|
94
|
+
addNumber(target, keyPrefix, Number.parseFloat(value));
|
|
97
95
|
}
|
|
98
96
|
}
|
|
99
97
|
break;
|
|
100
|
-
|
|
98
|
+
}
|
|
99
|
+
case 'boolean': {
|
|
101
100
|
{
|
|
102
101
|
// skip
|
|
103
102
|
}
|
|
104
103
|
break;
|
|
104
|
+
}
|
|
105
105
|
|
|
106
|
-
default:
|
|
106
|
+
default: {
|
|
107
107
|
log.error(
|
|
108
108
|
'Unhandled value type %s found when adding data for %s ',
|
|
109
109
|
valueType,
|
|
110
110
|
keyPrefix
|
|
111
111
|
);
|
|
112
|
+
}
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
@@ -120,6 +121,7 @@ class Statistics {
|
|
|
120
121
|
options = options || {};
|
|
121
122
|
let percentiles = options.percentiles || [0, 10, 90, 99, 100];
|
|
122
123
|
|
|
124
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
123
125
|
return Object.keys(this.data).reduce((results, key) => {
|
|
124
126
|
let decimals = options.decimals || 0;
|
|
125
127
|
let stats = get(this.data, [key]);
|
|
@@ -131,16 +133,16 @@ class Statistics {
|
|
|
131
133
|
decimals = 4;
|
|
132
134
|
}
|
|
133
135
|
const node = {
|
|
134
|
-
median: parseFloat(stats.median().toFixed(decimals)),
|
|
135
|
-
mean: parseFloat(stats.amean().toFixed(decimals)),
|
|
136
|
-
mdev: parseFloat(
|
|
136
|
+
median: Number.parseFloat(stats.median().toFixed(decimals)),
|
|
137
|
+
mean: Number.parseFloat(stats.amean().toFixed(decimals)),
|
|
138
|
+
mdev: Number.parseFloat(
|
|
137
139
|
stats.stddev() / Math.sqrt(stats.length).toFixed(decimals)
|
|
138
140
|
) // "standard deviation of the mean"
|
|
139
141
|
};
|
|
140
|
-
|
|
142
|
+
for (const p of percentiles) {
|
|
141
143
|
let name = percentileName(p);
|
|
142
|
-
node[name] = parseFloat(stats.percentile(p).toFixed(decimals));
|
|
143
|
-
}
|
|
144
|
+
node[name] = Number.parseFloat(stats.percentile(p).toFixed(decimals));
|
|
145
|
+
}
|
|
144
146
|
set(results, [key], node);
|
|
145
147
|
|
|
146
148
|
return results;
|
|
@@ -165,19 +167,23 @@ class Statistics {
|
|
|
165
167
|
decimals = 4;
|
|
166
168
|
}
|
|
167
169
|
const results = {
|
|
168
|
-
median: parseFloat(stats.median().toFixed(decimals)),
|
|
169
|
-
mean: parseFloat(stats.amean().toFixed(decimals)),
|
|
170
|
-
mdev: parseFloat(
|
|
170
|
+
median: Number.parseFloat(stats.median().toFixed(decimals)),
|
|
171
|
+
mean: Number.parseFloat(stats.amean().toFixed(decimals)),
|
|
172
|
+
mdev: Number.parseFloat(
|
|
173
|
+
(stats.stddev() / Math.sqrt(stats.length)).toFixed(4)
|
|
174
|
+
), // "standard deviation of the mean"
|
|
171
175
|
rsd:
|
|
172
176
|
stats.stddev() > 0
|
|
173
|
-
? parseFloat((100 * stats.stddev()) / stats.amean())
|
|
177
|
+
? Number.parseFloat((100 * stats.stddev()) / stats.amean())
|
|
174
178
|
: 0, // Relative standard deviation
|
|
175
|
-
stddev: parseFloat(stats.stddev().toFixed(decimals))
|
|
179
|
+
stddev: Number.parseFloat(stats.stddev().toFixed(decimals))
|
|
176
180
|
};
|
|
177
|
-
|
|
181
|
+
for (const p of percentiles) {
|
|
178
182
|
let name = percentileName(p);
|
|
179
|
-
results[name] = parseFloat(
|
|
180
|
-
|
|
183
|
+
results[name] = Number.parseFloat(
|
|
184
|
+
stats.percentile(p).toFixed(decimals)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
181
187
|
return results;
|
|
182
188
|
}
|
|
183
189
|
|
|
@@ -185,12 +191,12 @@ class Statistics {
|
|
|
185
191
|
if (data instanceof Stats) {
|
|
186
192
|
set(target, keyPrefix, summarize(data));
|
|
187
193
|
} else if (typeof data === 'object') {
|
|
188
|
-
Object.keys(data)
|
|
189
|
-
const path = keyPrefix
|
|
194
|
+
for (const key of Object.keys(data)) {
|
|
195
|
+
const path = [...keyPrefix, key];
|
|
190
196
|
summarizeRecursive(target, path, data[key]);
|
|
191
|
-
}
|
|
197
|
+
}
|
|
192
198
|
} else {
|
|
193
|
-
throw new
|
|
199
|
+
throw new TypeError(
|
|
194
200
|
'Unhandled data type ' + typeof data + ' found when summarizing data'
|
|
195
201
|
);
|
|
196
202
|
}
|
|
@@ -201,7 +207,3 @@ class Statistics {
|
|
|
201
207
|
return result;
|
|
202
208
|
}
|
|
203
209
|
}
|
|
204
|
-
|
|
205
|
-
module.exports = {
|
|
206
|
-
Statistics: Statistics
|
|
207
|
-
};
|
package/lib/support/stop.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import intel from 'intel';
|
|
3
|
+
const log = intel.getLogger('browsertime');
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
module.exports = async function (processName) {
|
|
7
|
-
const scriptArgs = ['-9', processName];
|
|
5
|
+
export default async function (processName) {
|
|
6
|
+
const scriptArguments = ['-9', processName];
|
|
8
7
|
|
|
9
8
|
log.debug('Kill all processes ' + processName);
|
|
10
9
|
|
|
11
|
-
return execa('pkill',
|
|
12
|
-
}
|
|
10
|
+
return execa('pkill', scriptArguments, { reject: false });
|
|
11
|
+
}
|
|
@@ -1,33 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
1
|
+
import { resolve as _resolve, join } from 'node:path';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { gunzip as _gunzip, gzip as _gzip, createGzip } from 'node:zlib';
|
|
4
|
+
import { parse } from 'node:url';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import {
|
|
7
|
+
writeFile as _writeFile,
|
|
8
|
+
readFile as _readFile,
|
|
9
|
+
unlink as _unlink,
|
|
10
|
+
mkdir as _mkdir,
|
|
11
|
+
createReadStream,
|
|
12
|
+
createWriteStream
|
|
13
|
+
} from 'node:fs';
|
|
14
|
+
import isEmpty from 'lodash.isempty';
|
|
15
|
+
import intel from 'intel';
|
|
16
|
+
import dayjs from 'dayjs';
|
|
17
|
+
const gunzip = promisify(_gunzip);
|
|
18
|
+
const writeFile = promisify(_writeFile);
|
|
19
|
+
const readFile = promisify(_readFile);
|
|
20
|
+
const gzip = promisify(_gzip);
|
|
21
|
+
const unlink = promisify(_unlink);
|
|
22
|
+
const mkdir = promisify(_mkdir);
|
|
23
|
+
const log = intel.getLogger('browsertime');
|
|
18
24
|
|
|
19
25
|
const defaultDir = 'browsertime-results';
|
|
20
26
|
let timestamp = dayjs().format().replace(/:/g, '');
|
|
21
27
|
|
|
22
28
|
function pathNameFromUrl(url) {
|
|
23
|
-
const parsedUrl =
|
|
29
|
+
const parsedUrl = parse(url),
|
|
24
30
|
pathSegments = parsedUrl.pathname.split('/');
|
|
25
31
|
|
|
26
32
|
pathSegments.unshift(parsedUrl.hostname);
|
|
27
33
|
|
|
28
34
|
if (!isEmpty(parsedUrl.search)) {
|
|
29
|
-
const md5 =
|
|
30
|
-
hash = md5.update(parsedUrl.search).digest('hex').
|
|
35
|
+
const md5 = createHash('md5'),
|
|
36
|
+
hash = md5.update(parsedUrl.search).digest('hex').slice(0, 8);
|
|
31
37
|
pathSegments.push('query-' + hash);
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -45,11 +51,9 @@ async function streamToString(stream) {
|
|
|
45
51
|
|
|
46
52
|
class StorageManager {
|
|
47
53
|
constructor(url, { resultDir, prettyPrint = false } = {}) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.baseDir = path.resolve(defaultDir, pathNameFromUrl(url), timestamp);
|
|
52
|
-
}
|
|
54
|
+
this.baseDir = resultDir
|
|
55
|
+
? _resolve(resultDir)
|
|
56
|
+
: _resolve(defaultDir, pathNameFromUrl(url), timestamp);
|
|
53
57
|
this.jsonIndentation = prettyPrint ? 2 : 0;
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -59,23 +63,21 @@ class StorageManager {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
async createSubDataDir(...name) {
|
|
62
|
-
const dir =
|
|
66
|
+
const dir = join(this.baseDir, ...name);
|
|
63
67
|
await mkdir(dir, { recursive: true });
|
|
64
68
|
return dir;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
async rm(filename) {
|
|
68
|
-
return unlink(
|
|
72
|
+
return unlink(join(this.baseDir, filename));
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
async writeData(filename, data, subdir) {
|
|
72
76
|
let dirPath;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
const fullPath = path.join(dirPath, filename);
|
|
77
|
+
dirPath = await (subdir
|
|
78
|
+
? this.createSubDataDir(subdir)
|
|
79
|
+
: this.createDataDir());
|
|
80
|
+
const fullPath = join(dirPath, filename);
|
|
79
81
|
return writeFile(fullPath, data).then(() => {
|
|
80
82
|
return fullPath;
|
|
81
83
|
});
|
|
@@ -86,21 +88,19 @@ class StorageManager {
|
|
|
86
88
|
const data = await gzip(Buffer.from(JSON.stringify(json)), { level: 1 });
|
|
87
89
|
return this.writeData(`${filename}.gz`, data);
|
|
88
90
|
} else {
|
|
89
|
-
const data = JSON.stringify(json,
|
|
91
|
+
const data = JSON.stringify(json, undefined, this.jsonIndentation);
|
|
90
92
|
return this.writeData(filename, data);
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
async readData(filename, subdir) {
|
|
95
97
|
let filepath;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
filepath = path.join(this.baseDir, filename);
|
|
100
|
-
}
|
|
98
|
+
filepath = subdir
|
|
99
|
+
? join(this.baseDir, subdir, filename)
|
|
100
|
+
: join(this.baseDir, filename);
|
|
101
101
|
|
|
102
102
|
if (filename.endsWith('.gz')) {
|
|
103
|
-
const readStream =
|
|
103
|
+
const readStream = createReadStream(filepath);
|
|
104
104
|
const text = await streamToString(readStream);
|
|
105
105
|
const unzipped = await gunzip(text);
|
|
106
106
|
return unzipped.toString();
|
|
@@ -111,9 +111,9 @@ class StorageManager {
|
|
|
111
111
|
|
|
112
112
|
async gzip(inputFile, outputFile, removeInput) {
|
|
113
113
|
const promise = new Promise(function (resolve, reject) {
|
|
114
|
-
const gzip =
|
|
115
|
-
const input =
|
|
116
|
-
const out =
|
|
114
|
+
const gzip = createGzip();
|
|
115
|
+
const input = createReadStream(inputFile);
|
|
116
|
+
const out = createWriteStream(outputFile);
|
|
117
117
|
out.on('finish', function () {
|
|
118
118
|
if (removeInput) {
|
|
119
119
|
unlink(inputFile).then(() => resolve());
|
|
@@ -135,4 +135,4 @@ class StorageManager {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
export default StorageManager;
|
package/lib/support/tcpdump.js
CHANGED
|
@@ -1,41 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const fileUtil = require('./fileUtil');
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import pathToFolder from './pathToFolder.js';
|
|
4
|
+
import { rename } from './fileUtil.js';
|
|
6
5
|
|
|
7
|
-
class TCPDump {
|
|
6
|
+
export default class TCPDump {
|
|
8
7
|
constructor(directory, options) {
|
|
9
8
|
this.baseDir = directory;
|
|
10
9
|
this.options = options;
|
|
11
10
|
}
|
|
12
11
|
async start(iteration) {
|
|
13
|
-
const captureFile =
|
|
14
|
-
const
|
|
12
|
+
const captureFile = join(this.baseDir, iteration + '.pcap');
|
|
13
|
+
const parameters = [
|
|
14
|
+
'tcpdump',
|
|
15
|
+
'-i',
|
|
16
|
+
'any',
|
|
17
|
+
'-s',
|
|
18
|
+
'0',
|
|
19
|
+
'-p',
|
|
20
|
+
'-w',
|
|
21
|
+
captureFile
|
|
22
|
+
];
|
|
15
23
|
if (this.options.tcpdumpPacketBuffered) {
|
|
16
|
-
|
|
24
|
+
parameters.push('-U');
|
|
17
25
|
}
|
|
18
26
|
if (this.options.tcpDumpParams) {
|
|
19
27
|
const extras = Array.isArray(this.options.tcpDumpParams)
|
|
20
28
|
? this.options.tcpDumpParams
|
|
21
29
|
: [this.options.tcpDumpParams];
|
|
22
|
-
|
|
30
|
+
parameters.push(...extras);
|
|
23
31
|
}
|
|
24
|
-
this.tcpdumpProcess = execa('sudo',
|
|
32
|
+
this.tcpdumpProcess = execa('sudo', parameters);
|
|
25
33
|
}
|
|
26
34
|
async stop() {
|
|
27
35
|
return execa('sudo', ['pkill', '-9', 'tcpdump'], { reject: false });
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
async mv(url, iteration) {
|
|
31
|
-
const oldLocation =
|
|
32
|
-
const newLocation =
|
|
39
|
+
const oldLocation = join(this.baseDir, iteration + '.pcap');
|
|
40
|
+
const newLocation = join(
|
|
33
41
|
this.baseDir,
|
|
34
42
|
pathToFolder(url, this.options),
|
|
35
43
|
iteration + '.pcap'
|
|
36
44
|
);
|
|
37
|
-
return
|
|
45
|
+
return rename(oldLocation, newLocation);
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
|
-
|
|
41
|
-
module.exports = TCPDump;
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
);
|
|
9
|
-
userTimings.measures = userTimings.measures.filter(measure =>
|
|
10
|
-
allowed.test(measure.name)
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
1
|
+
export function filterWhitelisted(userTimings, whitelistRegex) {
|
|
2
|
+
const allowed = new RegExp(whitelistRegex);
|
|
3
|
+
userTimings.marks = userTimings.marks.filter(mark => allowed.test(mark.name));
|
|
4
|
+
userTimings.measures = userTimings.measures.filter(measure =>
|
|
5
|
+
allowed.test(measure.name)
|
|
6
|
+
);
|
|
7
|
+
}
|