@elastic/synthetics 1.0.0-beta.2 → 1.0.0-beta.22
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 +235 -0
- package/NOTICE.txt +250 -0
- package/README.md +2 -1
- package/dist/cli.js +87 -56
- package/dist/cli.js.map +1 -1
- package/dist/common_types.d.ts +116 -14
- package/dist/common_types.d.ts.map +1 -1
- package/dist/config.d.ts +2 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -5
- package/dist/config.js.map +1 -1
- package/dist/core/browser-service.js +5 -5
- package/dist/core/browser-service.js.map +1 -1
- package/dist/core/expect.d.ts +177 -0
- package/dist/core/expect.d.ts.map +1 -0
- package/dist/core/expect.js +33 -0
- package/dist/core/expect.js.map +1 -0
- package/dist/core/gatherer.d.ts +4 -7
- package/dist/core/gatherer.d.ts.map +1 -1
- package/dist/core/gatherer.js +39 -12
- package/dist/core/gatherer.js.map +1 -1
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/logger.d.ts +0 -1
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +3 -16
- package/dist/core/logger.js.map +1 -1
- package/dist/core/runner.d.ts +54 -45
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +181 -71
- package/dist/core/runner.js.map +1 -1
- package/dist/dsl/journey.d.ts +13 -4
- package/dist/dsl/journey.d.ts.map +1 -1
- package/dist/dsl/journey.js +40 -1
- package/dist/dsl/journey.js.map +1 -1
- package/dist/formatter/javascript.d.ts +73 -0
- package/dist/formatter/javascript.d.ts.map +1 -0
- package/dist/formatter/javascript.js +274 -0
- package/dist/formatter/javascript.js.map +1 -0
- package/dist/helpers.d.ts +33 -12
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +108 -54
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/dist/parse_args.d.ts +5 -2
- package/dist/parse_args.d.ts.map +1 -1
- package/dist/parse_args.js +59 -16
- package/dist/parse_args.js.map +1 -1
- package/dist/plugins/browser-console.d.ts +5 -8
- package/dist/plugins/browser-console.d.ts.map +1 -1
- package/dist/plugins/browser-console.js +27 -8
- package/dist/plugins/browser-console.js.map +1 -1
- package/dist/plugins/network.d.ts +23 -20
- package/dist/plugins/network.d.ts.map +1 -1
- package/dist/plugins/network.js +149 -124
- package/dist/plugins/network.js.map +1 -1
- package/dist/plugins/performance.d.ts +5 -5
- package/dist/plugins/performance.d.ts.map +1 -1
- package/dist/plugins/performance.js +5 -5
- package/dist/plugins/performance.js.map +1 -1
- package/dist/plugins/plugin-manager.d.ts +11 -11
- package/dist/plugins/plugin-manager.d.ts.map +1 -1
- package/dist/plugins/plugin-manager.js +40 -24
- package/dist/plugins/plugin-manager.js.map +1 -1
- package/dist/plugins/tracing.d.ts +10 -25
- package/dist/plugins/tracing.d.ts.map +1 -1
- package/dist/plugins/tracing.js +40 -26
- package/dist/plugins/tracing.js.map +1 -1
- package/dist/reporters/base.d.ts +0 -1
- package/dist/reporters/base.d.ts.map +1 -1
- package/dist/reporters/base.js +34 -32
- package/dist/reporters/base.js.map +1 -1
- package/dist/reporters/json.d.ts +46 -59
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +188 -70
- package/dist/reporters/json.js.map +1 -1
- package/dist/reporters/junit.d.ts.map +1 -1
- package/dist/reporters/junit.js +7 -7
- package/dist/reporters/junit.js.map +1 -1
- package/dist/sdk/lh-trace-processor.d.ts +304 -0
- package/dist/sdk/lh-trace-processor.d.ts.map +1 -0
- package/dist/sdk/lh-trace-processor.js +832 -0
- package/dist/sdk/lh-trace-processor.js.map +1 -0
- package/dist/sdk/trace-metrics.d.ts +61 -0
- package/dist/sdk/trace-metrics.d.ts.map +1 -0
- package/dist/sdk/trace-metrics.js +252 -0
- package/dist/sdk/trace-metrics.js.map +1 -0
- package/dist/sdk/trace-processor.d.ts +106 -0
- package/dist/sdk/trace-processor.d.ts.map +1 -0
- package/dist/sdk/trace-processor.js +74 -0
- package/dist/sdk/trace-processor.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1575
- package/package.json +27 -16
- package/src/cli.ts +68 -45
- package/src/common_types.ts +138 -14
- package/src/config.ts +2 -5
- package/src/core/expect.ts +187 -0
- package/src/core/gatherer.ts +48 -24
- package/src/core/index.ts +6 -6
- package/src/core/logger.ts +1 -12
- package/src/core/runner.ts +259 -112
- package/src/dsl/journey.ts +28 -6
- package/src/formatter/javascript.ts +330 -0
- package/src/helpers.ts +114 -24
- package/src/index.ts +7 -6
- package/src/parse_args.ts +97 -22
- package/src/plugins/browser-console.ts +30 -13
- package/src/plugins/network.ts +175 -159
- package/src/plugins/performance.ts +7 -7
- package/src/plugins/plugin-manager.ts +47 -36
- package/src/plugins/tracing.ts +43 -54
- package/src/reporters/base.ts +29 -23
- package/src/reporters/json.ts +259 -80
- package/src/reporters/junit.ts +1 -1
- package/src/sdk/lh-trace-processor.ts +1033 -0
- package/src/sdk/trace-metrics.ts +287 -0
- package/src/sdk/trace-processor.ts +147 -0
package/src/parse_args.ts
CHANGED
|
@@ -23,15 +23,14 @@
|
|
|
23
23
|
*
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { program } from 'commander';
|
|
26
|
+
import { program, Option } from 'commander';
|
|
27
|
+
import { CliArgs } from './common_types';
|
|
27
28
|
import { reporters } from './reporters';
|
|
28
|
-
|
|
29
|
-
const availableReporters = Object.keys(reporters)
|
|
30
|
-
.map(r => String(r))
|
|
31
|
-
.join();
|
|
29
|
+
import { DEFAULT_NETWORK_CONDITIONS_ARG } from './helpers';
|
|
32
30
|
|
|
33
31
|
/* eslint-disable-next-line @typescript-eslint/no-var-requires */
|
|
34
32
|
const { name, version } = require('../package.json');
|
|
33
|
+
|
|
35
34
|
program
|
|
36
35
|
.name(`npx ${name}`)
|
|
37
36
|
.usage('[options] [dir] [files] file')
|
|
@@ -39,12 +38,15 @@ program
|
|
|
39
38
|
'-c, --config <path>',
|
|
40
39
|
'configuration path (default: synthetics.config.js)'
|
|
41
40
|
)
|
|
42
|
-
.option('-s, --suite-params <jsonstring>', 'suite variables', '{}')
|
|
43
|
-
.option('-e, --environment <envname>', 'e.g. production', 'development')
|
|
44
|
-
.option('-j, --json', 'output newline delimited JSON')
|
|
45
41
|
.option(
|
|
46
|
-
'--
|
|
47
|
-
|
|
42
|
+
'-p, --params <jsonstring>',
|
|
43
|
+
'JSON object that gets injected to all journeys',
|
|
44
|
+
JSON.parse
|
|
45
|
+
)
|
|
46
|
+
.addOption(
|
|
47
|
+
new Option('--reporter <value>', `output repoter format`).choices(
|
|
48
|
+
Object.keys(reporters)
|
|
49
|
+
)
|
|
48
50
|
)
|
|
49
51
|
.option('-d, --debug', 'print debug logs info')
|
|
50
52
|
.option(
|
|
@@ -54,30 +56,103 @@ program
|
|
|
54
56
|
.option('--inline', 'Run inline journeys from heartbeat')
|
|
55
57
|
.option('-r, --require <modules...>', 'module(s) to preload')
|
|
56
58
|
.option('--no-headless', 'run browser in headful mode')
|
|
57
|
-
.option('--sandbox', 'enable chromium sandboxing')
|
|
59
|
+
.option('--sandbox', 'enable chromium sandboxing', false)
|
|
60
|
+
.option('--rich-events', 'Mimics a heartbeat run')
|
|
58
61
|
.option(
|
|
59
|
-
'--
|
|
60
|
-
'
|
|
62
|
+
'--capability <features...>',
|
|
63
|
+
'Enable capabilities through feature flags'
|
|
61
64
|
)
|
|
62
|
-
.
|
|
63
|
-
'--
|
|
64
|
-
|
|
65
|
+
.addOption(
|
|
66
|
+
new Option('--screenshots [flag]', 'take screenshots at end of each step')
|
|
67
|
+
.choices(['on', 'off', 'only-on-failure'])
|
|
68
|
+
.default('on')
|
|
65
69
|
)
|
|
66
|
-
.option('--screenshots', 'take screenshot for each step')
|
|
67
|
-
.option('--filmstrips', 'record detailed filmstrip info for all journeys')
|
|
68
|
-
.option('--network', 'capture network information for all journeys')
|
|
69
|
-
.option('--metrics', 'capture performance metrics for each step')
|
|
70
70
|
.option(
|
|
71
71
|
'--dry-run',
|
|
72
72
|
"don't actually execute anything, report only registered journeys"
|
|
73
73
|
)
|
|
74
|
-
.option(
|
|
74
|
+
.option(
|
|
75
|
+
'--match <name>',
|
|
76
|
+
'run only journeys with a name or tags that matches the glob'
|
|
77
|
+
)
|
|
78
|
+
.option(
|
|
79
|
+
'--tags <name...>',
|
|
80
|
+
'run only journeys with the given tag(s), or globs'
|
|
81
|
+
)
|
|
75
82
|
.option(
|
|
76
83
|
'--outfd <fd>',
|
|
77
84
|
'specify a file descriptor for logs. Default is stdout',
|
|
78
85
|
parseInt
|
|
79
86
|
)
|
|
87
|
+
.option(
|
|
88
|
+
'--ws-endpoint <endpoint>',
|
|
89
|
+
'Browser WebSocket endpoint to connect to'
|
|
90
|
+
)
|
|
91
|
+
.option(
|
|
92
|
+
'--pause-on-error',
|
|
93
|
+
'pause on error until a keypress is made in the console. Useful during development'
|
|
94
|
+
)
|
|
95
|
+
.option(
|
|
96
|
+
'--ignore-https-errors',
|
|
97
|
+
'ignores any HTTPS errors in sites being tested, including ones related to unrecognized certs or signatures. This can be insecure!'
|
|
98
|
+
)
|
|
99
|
+
.option(
|
|
100
|
+
'--quiet-exit-code',
|
|
101
|
+
'always return 0 as an exit code status, regardless of test pass / fail. Only return > 0 exit codes on internal errors where the suite could not be run'
|
|
102
|
+
)
|
|
103
|
+
.addOption(
|
|
104
|
+
new Option(
|
|
105
|
+
'--throttling <d/u/l>',
|
|
106
|
+
'List of options to throttle network conditions for download throughput (d) in megabits/second, upload throughput (u) in megabits/second and latency (l) in milliseconds.'
|
|
107
|
+
).default(DEFAULT_NETWORK_CONDITIONS_ARG)
|
|
108
|
+
)
|
|
109
|
+
.option('--no-throttling', 'Turns off default network throttling.')
|
|
110
|
+
.option('--playwright-options <jsonstring>', 'JSON object to pass in custom Playwright options for the agent. Options passed will be merged with Playwright options defined in your synthetics.config.js file. Options defined via --playwright-options take precedence.', JSON.parse)
|
|
80
111
|
.version(version)
|
|
81
112
|
.description('Run synthetic tests');
|
|
82
113
|
|
|
83
|
-
|
|
114
|
+
const command = program.parse(process.argv);
|
|
115
|
+
const options = command.opts() as CliArgs;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Group all events that can be consumed by heartbeat and
|
|
119
|
+
* eventually by the Synthetics UI.
|
|
120
|
+
*/
|
|
121
|
+
if (options.richEvents) {
|
|
122
|
+
options.reporter = options.reporter ?? 'json';
|
|
123
|
+
options.ssblocks = true;
|
|
124
|
+
options.network = true;
|
|
125
|
+
options.quietExitCode = true;
|
|
126
|
+
options.trace = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (options.capability) {
|
|
130
|
+
const supportedCapabilities = [
|
|
131
|
+
'trace',
|
|
132
|
+
'network',
|
|
133
|
+
'filmstrips',
|
|
134
|
+
'metrics',
|
|
135
|
+
'ssblocks',
|
|
136
|
+
];
|
|
137
|
+
/**
|
|
138
|
+
* trace - record chrome trace events(LCP, FCP, CLS, etc.) for all journeys
|
|
139
|
+
* network - capture network information for all journeys
|
|
140
|
+
* filmstrips - record detailed filmstrips for all journeys
|
|
141
|
+
* metrics - capture performance metrics (DOM Nodes, Heap size, etc.) for each step
|
|
142
|
+
* ssblocks - Dedupes the screenshots in to blocks to save storage space
|
|
143
|
+
*/
|
|
144
|
+
for (const flag of options.capability) {
|
|
145
|
+
if (supportedCapabilities.includes(flag)) {
|
|
146
|
+
options[flag] = true;
|
|
147
|
+
} else {
|
|
148
|
+
console.warn(
|
|
149
|
+
`Missing capability "${flag}", current supported capabilities are ${supportedCapabilities.join(
|
|
150
|
+
', '
|
|
151
|
+
)}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export { options };
|
|
158
|
+
export default command;
|
|
@@ -23,22 +23,18 @@
|
|
|
23
23
|
*
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import {
|
|
27
|
-
import { DefaultPluginOutput } from '../common_types';
|
|
26
|
+
import { BrowserMessage, Driver } from '../common_types';
|
|
28
27
|
import { Step } from '../dsl';
|
|
29
28
|
import { getTimestamp } from '../helpers';
|
|
30
29
|
|
|
31
|
-
export type BrowserMessage = {
|
|
32
|
-
text: string;
|
|
33
|
-
type: string;
|
|
34
|
-
} & DefaultPluginOutput;
|
|
35
|
-
|
|
36
30
|
const defaultMessageLimit = 1000;
|
|
37
31
|
|
|
38
32
|
export class BrowserConsole {
|
|
39
33
|
private messages: BrowserMessage[] = [];
|
|
40
34
|
_currentStep: Partial<Step> = null;
|
|
41
35
|
|
|
36
|
+
constructor(private driver: Driver) {}
|
|
37
|
+
|
|
42
38
|
private consoleEventListener = msg => {
|
|
43
39
|
if (!this._currentStep) {
|
|
44
40
|
return;
|
|
@@ -52,20 +48,41 @@ export class BrowserConsole {
|
|
|
52
48
|
type,
|
|
53
49
|
step: { name, index },
|
|
54
50
|
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
51
|
+
|
|
52
|
+
this.enforceMessagesLimit();
|
|
58
53
|
}
|
|
59
54
|
};
|
|
60
55
|
|
|
61
|
-
|
|
56
|
+
private pageErrorEventListener = (error: Error) => {
|
|
57
|
+
if (!this._currentStep) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const { name, index } = this._currentStep;
|
|
61
|
+
this.messages.push({
|
|
62
|
+
timestamp: getTimestamp(),
|
|
63
|
+
text: error.message,
|
|
64
|
+
type: 'error',
|
|
65
|
+
step: { name, index },
|
|
66
|
+
error,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.enforceMessagesLimit();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
private enforceMessagesLimit() {
|
|
73
|
+
if (this.messages.length > defaultMessageLimit) {
|
|
74
|
+
this.messages.splice(0, 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
62
77
|
|
|
63
78
|
start() {
|
|
64
|
-
this.page.on('console', this.consoleEventListener);
|
|
79
|
+
this.driver.page.on('console', this.consoleEventListener);
|
|
80
|
+
this.driver.page.on('pageerror', this.pageErrorEventListener);
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
stop() {
|
|
68
|
-
this.page.off('console', this.consoleEventListener);
|
|
84
|
+
this.driver.page.off('console', this.consoleEventListener);
|
|
85
|
+
this.driver.page.off('pageerror', this.pageErrorEventListener);
|
|
69
86
|
return this.messages;
|
|
70
87
|
}
|
|
71
88
|
}
|
package/src/plugins/network.ts
CHANGED
|
@@ -23,9 +23,8 @@
|
|
|
23
23
|
*
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { CDPSession } from 'playwright-chromium';
|
|
27
26
|
import { Protocol } from 'playwright-chromium/types/protocol';
|
|
28
|
-
import { NetworkInfo, BrowserInfo } from '../common_types';
|
|
27
|
+
import { NetworkInfo, BrowserInfo, Driver } from '../common_types';
|
|
29
28
|
import { Step } from '../dsl';
|
|
30
29
|
import { getTimestamp } from '../helpers';
|
|
31
30
|
|
|
@@ -35,7 +34,10 @@ export class NetworkManager {
|
|
|
35
34
|
results: Array<NetworkInfo> = [];
|
|
36
35
|
_currentStep: Partial<Step> = null;
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
constructor(private driver: Driver) {}
|
|
38
|
+
|
|
39
|
+
async start() {
|
|
40
|
+
const { client } = this.driver;
|
|
39
41
|
const { product } = await client.send('Browser.getVersion');
|
|
40
42
|
const [name, version] = product.split('/');
|
|
41
43
|
this._browser = { name, version };
|
|
@@ -62,14 +64,14 @@ export class NetworkManager {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
_onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload) {
|
|
65
|
-
const {
|
|
66
|
-
const { url, method } = request;
|
|
67
|
+
const { url, method, postData, headers } = event.request;
|
|
67
68
|
/**
|
|
68
69
|
* Data URI should not show up as network requests
|
|
69
70
|
*/
|
|
70
71
|
if (url.startsWith('data:')) {
|
|
71
72
|
return;
|
|
72
73
|
}
|
|
74
|
+
const { requestId, timestamp, type, loaderId } = event;
|
|
73
75
|
const isNavigationRequest = requestId == loaderId && type === 'Document';
|
|
74
76
|
const record = this._findNetworkRecord(requestId);
|
|
75
77
|
/**
|
|
@@ -79,6 +81,7 @@ export class NetworkManager {
|
|
|
79
81
|
*/
|
|
80
82
|
if (record) {
|
|
81
83
|
if (event.redirectResponse) {
|
|
84
|
+
record.response.redirectURL = url;
|
|
82
85
|
const response = event.redirectResponse;
|
|
83
86
|
const data = Object.assign(event, {
|
|
84
87
|
type: event.type,
|
|
@@ -115,15 +118,23 @@ export class NetworkManager {
|
|
|
115
118
|
step: this._currentStep,
|
|
116
119
|
timestamp: getTimestamp(),
|
|
117
120
|
url,
|
|
118
|
-
request,
|
|
119
121
|
type,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
request: {
|
|
123
|
+
url,
|
|
124
|
+
method,
|
|
125
|
+
headers,
|
|
126
|
+
bytes: postData?.length || 0,
|
|
127
|
+
},
|
|
128
|
+
response: {
|
|
129
|
+
status: -1,
|
|
130
|
+
mimeType: 'x-unknown',
|
|
131
|
+
headers: {},
|
|
132
|
+
timing: null,
|
|
133
|
+
},
|
|
122
134
|
isNavigationRequest,
|
|
123
|
-
|
|
124
|
-
loadEndTime: -1,
|
|
135
|
+
requestSentTime: timestamp,
|
|
125
136
|
responseReceivedTime: -1,
|
|
126
|
-
|
|
137
|
+
loadEndTime: -1,
|
|
127
138
|
resourceSize: 0,
|
|
128
139
|
transferSize: 0,
|
|
129
140
|
timings: null,
|
|
@@ -145,6 +156,7 @@ export class NetworkManager {
|
|
|
145
156
|
...record.request.headers,
|
|
146
157
|
...headers,
|
|
147
158
|
};
|
|
159
|
+
record.request.referrer = record.request.headers?.Referer;
|
|
148
160
|
}
|
|
149
161
|
|
|
150
162
|
_onDataReceived(event: Protocol.Network.dataReceivedPayload) {
|
|
@@ -163,12 +175,31 @@ export class NetworkManager {
|
|
|
163
175
|
if (!record) {
|
|
164
176
|
return;
|
|
165
177
|
}
|
|
166
|
-
|
|
178
|
+
|
|
179
|
+
record.responseReceivedTime = timestamp;
|
|
180
|
+
record.transferSize = response.encodedDataLength;
|
|
181
|
+
record.response = {
|
|
182
|
+
url: response.url,
|
|
167
183
|
status: response.status,
|
|
168
|
-
response,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
184
|
+
protocol: response.protocol,
|
|
185
|
+
statusText: response.statusText,
|
|
186
|
+
headers: response.headers,
|
|
187
|
+
mimeType: response.mimeType,
|
|
188
|
+
remoteIPAddress: response.remoteIPAddress,
|
|
189
|
+
remotePort: response.remotePort,
|
|
190
|
+
fromServiceWorker: response.fromServiceWorker,
|
|
191
|
+
redirectURL: record.response.redirectURL,
|
|
192
|
+
timing: response.timing,
|
|
193
|
+
};
|
|
194
|
+
if (response.securityDetails) {
|
|
195
|
+
record.response.securityDetails = {
|
|
196
|
+
protocol: response.securityDetails.protocol,
|
|
197
|
+
subjectName: response.securityDetails.subjectName,
|
|
198
|
+
issuer: response.securityDetails.issuer,
|
|
199
|
+
validFrom: response.securityDetails.validFrom,
|
|
200
|
+
validTo: response.securityDetails.validTo,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
172
203
|
}
|
|
173
204
|
|
|
174
205
|
_onLoadingFinished(event: Protocol.Network.loadingFinishedPayload) {
|
|
@@ -192,9 +223,12 @@ export class NetworkManager {
|
|
|
192
223
|
}
|
|
193
224
|
if (encodedDataLength >= 0) {
|
|
194
225
|
record.transferSize = encodedDataLength;
|
|
226
|
+
record.response.body = {
|
|
227
|
+
bytes: encodedDataLength,
|
|
228
|
+
};
|
|
195
229
|
}
|
|
196
230
|
record.loadEndTime = endTime;
|
|
197
|
-
record.timings =
|
|
231
|
+
record.timings = calculateTimings(record);
|
|
198
232
|
this.results.push(record);
|
|
199
233
|
/**
|
|
200
234
|
* When there is DNS failure, browsers keeps retrying requests which
|
|
@@ -207,159 +241,141 @@ export class NetworkManager {
|
|
|
207
241
|
* Account for missing response received event and also adjust the
|
|
208
242
|
* response received event based on when first byte event was recorded
|
|
209
243
|
*/
|
|
210
|
-
getResponseReceivedTime(
|
|
211
|
-
timing: Protocol.Network.Response['timing'],
|
|
212
|
-
responseReceivedTime: number
|
|
213
|
-
) {
|
|
214
|
-
if (timing == null) {
|
|
215
|
-
return responseReceivedTime;
|
|
216
|
-
}
|
|
217
|
-
const startTime = timing.requestTime;
|
|
218
|
-
const headersReceivedTime = startTime + timing.receiveHeadersEnd / 1000;
|
|
219
|
-
if (
|
|
220
|
-
responseReceivedTime < 0 ||
|
|
221
|
-
responseReceivedTime > headersReceivedTime
|
|
222
|
-
) {
|
|
223
|
-
responseReceivedTime = headersReceivedTime;
|
|
224
|
-
}
|
|
225
|
-
if (startTime > responseReceivedTime) {
|
|
226
|
-
responseReceivedTime = startTime;
|
|
227
|
-
}
|
|
228
|
-
return responseReceivedTime;
|
|
229
|
-
}
|
|
230
244
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
calculateTimings(record: NetworkInfo) {
|
|
236
|
-
const result: NetworkInfo['timings'] = {
|
|
237
|
-
blocked: -1,
|
|
238
|
-
queueing: -1,
|
|
239
|
-
proxy: -1,
|
|
240
|
-
dns: -1,
|
|
241
|
-
ssl: -1,
|
|
242
|
-
connect: -1,
|
|
243
|
-
send: -1,
|
|
244
|
-
wait: -1,
|
|
245
|
-
receive: -1,
|
|
246
|
-
total: -1,
|
|
247
|
-
};
|
|
248
|
-
const { requestSentTime, loadEndTime, responseReceivedTime } = record;
|
|
245
|
+
stop() {
|
|
246
|
+
return this.results;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
250
|
+
function getResponseReceivedTime(
|
|
251
|
+
timing: Protocol.Network.Response['timing'],
|
|
252
|
+
responseReceivedTime: number
|
|
253
|
+
) {
|
|
254
|
+
if (timing == null) {
|
|
255
|
+
return responseReceivedTime;
|
|
256
|
+
}
|
|
257
|
+
const startTime = timing.requestTime;
|
|
258
|
+
const headersReceivedTime = startTime + timing.receiveHeadersEnd / 1000;
|
|
259
|
+
if (responseReceivedTime < 0 || responseReceivedTime > headersReceivedTime) {
|
|
260
|
+
responseReceivedTime = headersReceivedTime;
|
|
261
|
+
}
|
|
262
|
+
if (startTime > responseReceivedTime) {
|
|
263
|
+
responseReceivedTime = startTime;
|
|
264
|
+
}
|
|
265
|
+
return responseReceivedTime;
|
|
266
|
+
}
|
|
259
267
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
};
|
|
278
|
-
/**
|
|
279
|
-
* requestTime is baseline in seconds, rest of the timing data are ticks in milliseconds
|
|
280
|
-
* from the requestTime, so we calculate the offset from that
|
|
281
|
-
*/
|
|
282
|
-
const addOffsetRange = (
|
|
283
|
-
name: keyof NetworkInfo['timings'],
|
|
284
|
-
start: number,
|
|
285
|
-
end: number
|
|
286
|
-
) => {
|
|
287
|
-
if (start >= 0 && end >= 0) {
|
|
288
|
-
calculateDiffInMs(
|
|
289
|
-
name,
|
|
290
|
-
startTime + start / 1000,
|
|
291
|
-
startTime + end / 1000
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
const timing = response.timing;
|
|
296
|
-
const actResRcvdTime = this.getResponseReceivedTime(
|
|
297
|
-
timing,
|
|
298
|
-
responseReceivedTime
|
|
299
|
-
);
|
|
300
|
-
const issueTime = requestSentTime;
|
|
301
|
-
const startTime = timing == null ? -1 : timing.requestTime;
|
|
302
|
-
const endTime = firstPositive([loadEndTime, actResRcvdTime]) || startTime;
|
|
268
|
+
/**
|
|
269
|
+
* The timing calculations are based on the chrome devtools frontend
|
|
270
|
+
* https://github.com/ChromeDevTools/devtools-frontend/blob/7f5478d8ceb7586f23f4073ab5c2085dac1ec26a/front_end/network/RequestTimingView.js#L98-L193
|
|
271
|
+
*/
|
|
272
|
+
export function calculateTimings(record: NetworkInfo) {
|
|
273
|
+
const result: NetworkInfo['timings'] = {
|
|
274
|
+
blocked: -1,
|
|
275
|
+
queueing: -1,
|
|
276
|
+
proxy: -1,
|
|
277
|
+
dns: -1,
|
|
278
|
+
ssl: -1,
|
|
279
|
+
connect: -1,
|
|
280
|
+
send: -1,
|
|
281
|
+
wait: -1,
|
|
282
|
+
receive: -1,
|
|
283
|
+
total: -1,
|
|
284
|
+
};
|
|
303
285
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
286
|
+
const toMilliseconds = (time: number) => (time === -1 ? -1 : time * 1000);
|
|
287
|
+
const calculateDiffInMs = (
|
|
288
|
+
name: keyof NetworkInfo['timings'],
|
|
289
|
+
start: number,
|
|
290
|
+
end: number
|
|
291
|
+
) => {
|
|
292
|
+
if (start < Number.MAX_VALUE && start <= end) {
|
|
293
|
+
result[name] = toMilliseconds(end - start);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const firstPositive = (numbers: number[]) => {
|
|
297
|
+
for (let i = 0; i < numbers.length; ++i) {
|
|
298
|
+
if (numbers[i] > 0) {
|
|
299
|
+
return numbers[i];
|
|
316
300
|
}
|
|
317
|
-
return result;
|
|
318
301
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
302
|
+
return null;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* requestTime is baseline in seconds, rest of the timing data are ticks in milliseconds
|
|
306
|
+
* from the requestTime, so we calculate the offset from that
|
|
307
|
+
*/
|
|
308
|
+
const addOffsetRange = (
|
|
309
|
+
name: keyof NetworkInfo['timings'],
|
|
310
|
+
start: number,
|
|
311
|
+
end: number
|
|
312
|
+
) => {
|
|
313
|
+
if (start >= 0 && end >= 0) {
|
|
314
|
+
calculateDiffInMs(name, startTime + start / 1000, startTime + end / 1000);
|
|
326
315
|
}
|
|
327
|
-
|
|
316
|
+
};
|
|
317
|
+
const { requestSentTime, loadEndTime, responseReceivedTime, response } =
|
|
318
|
+
record;
|
|
319
|
+
const timing = response.timing;
|
|
320
|
+
const actResRcvdTime = getResponseReceivedTime(timing, responseReceivedTime);
|
|
321
|
+
const issueTime = requestSentTime;
|
|
322
|
+
const startTime = timing == null ? -1 : timing.requestTime;
|
|
323
|
+
const endTime = firstPositive([loadEndTime, actResRcvdTime]) || startTime;
|
|
328
324
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
addOffsetRange('proxy', timing.proxyStart, timing.proxyEnd);
|
|
342
|
-
addOffsetRange('dns', timing.dnsStart, timing.dnsEnd);
|
|
343
|
-
addOffsetRange('connect', timing.connectStart, timing.connectEnd);
|
|
344
|
-
addOffsetRange('ssl', timing.sslStart, timing.sslEnd);
|
|
345
|
-
addOffsetRange('send', timing.sendStart, timing.sendEnd);
|
|
346
|
-
addOffsetRange(
|
|
347
|
-
'wait',
|
|
348
|
-
Math.max(
|
|
349
|
-
timing.sendEnd,
|
|
350
|
-
timing.connectEnd,
|
|
351
|
-
timing.dnsEnd,
|
|
352
|
-
timing.proxyEnd,
|
|
353
|
-
blockingEnd
|
|
354
|
-
),
|
|
355
|
-
responseReceived
|
|
356
|
-
);
|
|
325
|
+
if (timing == null) {
|
|
326
|
+
const start =
|
|
327
|
+
issueTime !== -1 ? issueTime : startTime !== -1 ? startTime : 0;
|
|
328
|
+
const middle = actResRcvdTime === -1 ? Number.MAX_VALUE : actResRcvdTime;
|
|
329
|
+
const end = endTime === -1 ? Number.MAX_VALUE : endTime;
|
|
330
|
+
calculateDiffInMs('total', start, end);
|
|
331
|
+
calculateDiffInMs('blocked', start, middle);
|
|
332
|
+
calculateDiffInMs('receive', middle, end);
|
|
333
|
+
// Check if the request is blocked/stalled for the whole timeframe and
|
|
334
|
+
// calculate timings appropriately for that request
|
|
335
|
+
if (!isFinite(result['blocked'])) {
|
|
336
|
+
result['blocked'] = result['total'];
|
|
357
337
|
}
|
|
358
|
-
calculateDiffInMs('receive', actResRcvdTime, endTime);
|
|
359
338
|
return result;
|
|
360
339
|
}
|
|
340
|
+
calculateDiffInMs(
|
|
341
|
+
'total',
|
|
342
|
+
issueTime < startTime ? issueTime : startTime,
|
|
343
|
+
endTime
|
|
344
|
+
);
|
|
345
|
+
if (issueTime < startTime) {
|
|
346
|
+
calculateDiffInMs('queueing', issueTime, startTime);
|
|
347
|
+
}
|
|
348
|
+
const responseReceived = toMilliseconds(actResRcvdTime - startTime);
|
|
361
349
|
|
|
362
|
-
|
|
363
|
-
|
|
350
|
+
if (response.fromServiceWorker) {
|
|
351
|
+
addOffsetRange('blocked', 0, timing.workerStart);
|
|
352
|
+
addOffsetRange('wait', timing.sendEnd, responseReceived);
|
|
353
|
+
} else if (!timing.pushStart) {
|
|
354
|
+
const blockingEnd =
|
|
355
|
+
firstPositive([
|
|
356
|
+
timing.dnsStart,
|
|
357
|
+
timing.connectStart,
|
|
358
|
+
timing.sendStart,
|
|
359
|
+
responseReceived,
|
|
360
|
+
]) || 0;
|
|
361
|
+
addOffsetRange('blocked', 0, blockingEnd);
|
|
362
|
+
addOffsetRange('proxy', timing.proxyStart, timing.proxyEnd);
|
|
363
|
+
addOffsetRange('dns', timing.dnsStart, timing.dnsEnd);
|
|
364
|
+
addOffsetRange('connect', timing.connectStart, timing.connectEnd);
|
|
365
|
+
addOffsetRange('ssl', timing.sslStart, timing.sslEnd);
|
|
366
|
+
addOffsetRange('send', timing.sendStart, timing.sendEnd);
|
|
367
|
+
addOffsetRange(
|
|
368
|
+
'wait',
|
|
369
|
+
Math.max(
|
|
370
|
+
timing.sendEnd,
|
|
371
|
+
timing.connectEnd,
|
|
372
|
+
timing.dnsEnd,
|
|
373
|
+
timing.proxyEnd,
|
|
374
|
+
blockingEnd
|
|
375
|
+
),
|
|
376
|
+
responseReceived
|
|
377
|
+
);
|
|
364
378
|
}
|
|
379
|
+
calculateDiffInMs('receive', actResRcvdTime, endTime);
|
|
380
|
+
return result;
|
|
365
381
|
}
|