@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.
Files changed (122) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/NOTICE.txt +250 -0
  3. package/README.md +2 -1
  4. package/dist/cli.js +87 -56
  5. package/dist/cli.js.map +1 -1
  6. package/dist/common_types.d.ts +116 -14
  7. package/dist/common_types.d.ts.map +1 -1
  8. package/dist/config.d.ts +2 -5
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +5 -5
  11. package/dist/config.js.map +1 -1
  12. package/dist/core/browser-service.js +5 -5
  13. package/dist/core/browser-service.js.map +1 -1
  14. package/dist/core/expect.d.ts +177 -0
  15. package/dist/core/expect.d.ts.map +1 -0
  16. package/dist/core/expect.js +33 -0
  17. package/dist/core/expect.js.map +1 -0
  18. package/dist/core/gatherer.d.ts +4 -7
  19. package/dist/core/gatherer.d.ts.map +1 -1
  20. package/dist/core/gatherer.js +39 -12
  21. package/dist/core/gatherer.js.map +1 -1
  22. package/dist/core/index.d.ts +5 -5
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/core/index.js +2 -2
  25. package/dist/core/index.js.map +1 -1
  26. package/dist/core/logger.d.ts +0 -1
  27. package/dist/core/logger.d.ts.map +1 -1
  28. package/dist/core/logger.js +3 -16
  29. package/dist/core/logger.js.map +1 -1
  30. package/dist/core/runner.d.ts +54 -45
  31. package/dist/core/runner.d.ts.map +1 -1
  32. package/dist/core/runner.js +181 -71
  33. package/dist/core/runner.js.map +1 -1
  34. package/dist/dsl/journey.d.ts +13 -4
  35. package/dist/dsl/journey.d.ts.map +1 -1
  36. package/dist/dsl/journey.js +40 -1
  37. package/dist/dsl/journey.js.map +1 -1
  38. package/dist/formatter/javascript.d.ts +73 -0
  39. package/dist/formatter/javascript.d.ts.map +1 -0
  40. package/dist/formatter/javascript.js +274 -0
  41. package/dist/formatter/javascript.js.map +1 -0
  42. package/dist/helpers.d.ts +33 -12
  43. package/dist/helpers.d.ts.map +1 -1
  44. package/dist/helpers.js +108 -54
  45. package/dist/helpers.js.map +1 -1
  46. package/dist/index.d.ts +8 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +6 -7
  49. package/dist/index.js.map +1 -1
  50. package/dist/parse_args.d.ts +5 -2
  51. package/dist/parse_args.d.ts.map +1 -1
  52. package/dist/parse_args.js +59 -16
  53. package/dist/parse_args.js.map +1 -1
  54. package/dist/plugins/browser-console.d.ts +5 -8
  55. package/dist/plugins/browser-console.d.ts.map +1 -1
  56. package/dist/plugins/browser-console.js +27 -8
  57. package/dist/plugins/browser-console.js.map +1 -1
  58. package/dist/plugins/network.d.ts +23 -20
  59. package/dist/plugins/network.d.ts.map +1 -1
  60. package/dist/plugins/network.js +149 -124
  61. package/dist/plugins/network.js.map +1 -1
  62. package/dist/plugins/performance.d.ts +5 -5
  63. package/dist/plugins/performance.d.ts.map +1 -1
  64. package/dist/plugins/performance.js +5 -5
  65. package/dist/plugins/performance.js.map +1 -1
  66. package/dist/plugins/plugin-manager.d.ts +11 -11
  67. package/dist/plugins/plugin-manager.d.ts.map +1 -1
  68. package/dist/plugins/plugin-manager.js +40 -24
  69. package/dist/plugins/plugin-manager.js.map +1 -1
  70. package/dist/plugins/tracing.d.ts +10 -25
  71. package/dist/plugins/tracing.d.ts.map +1 -1
  72. package/dist/plugins/tracing.js +40 -26
  73. package/dist/plugins/tracing.js.map +1 -1
  74. package/dist/reporters/base.d.ts +0 -1
  75. package/dist/reporters/base.d.ts.map +1 -1
  76. package/dist/reporters/base.js +34 -32
  77. package/dist/reporters/base.js.map +1 -1
  78. package/dist/reporters/json.d.ts +46 -59
  79. package/dist/reporters/json.d.ts.map +1 -1
  80. package/dist/reporters/json.js +188 -70
  81. package/dist/reporters/json.js.map +1 -1
  82. package/dist/reporters/junit.d.ts.map +1 -1
  83. package/dist/reporters/junit.js +7 -7
  84. package/dist/reporters/junit.js.map +1 -1
  85. package/dist/sdk/lh-trace-processor.d.ts +304 -0
  86. package/dist/sdk/lh-trace-processor.d.ts.map +1 -0
  87. package/dist/sdk/lh-trace-processor.js +832 -0
  88. package/dist/sdk/lh-trace-processor.js.map +1 -0
  89. package/dist/sdk/trace-metrics.d.ts +61 -0
  90. package/dist/sdk/trace-metrics.d.ts.map +1 -0
  91. package/dist/sdk/trace-metrics.js +252 -0
  92. package/dist/sdk/trace-metrics.js.map +1 -0
  93. package/dist/sdk/trace-processor.d.ts +106 -0
  94. package/dist/sdk/trace-processor.d.ts.map +1 -0
  95. package/dist/sdk/trace-processor.js +74 -0
  96. package/dist/sdk/trace-processor.js.map +1 -0
  97. package/dist/tsconfig.tsbuildinfo +1 -1575
  98. package/package.json +27 -16
  99. package/src/cli.ts +68 -45
  100. package/src/common_types.ts +138 -14
  101. package/src/config.ts +2 -5
  102. package/src/core/expect.ts +187 -0
  103. package/src/core/gatherer.ts +48 -24
  104. package/src/core/index.ts +6 -6
  105. package/src/core/logger.ts +1 -12
  106. package/src/core/runner.ts +259 -112
  107. package/src/dsl/journey.ts +28 -6
  108. package/src/formatter/javascript.ts +330 -0
  109. package/src/helpers.ts +114 -24
  110. package/src/index.ts +7 -6
  111. package/src/parse_args.ts +97 -22
  112. package/src/plugins/browser-console.ts +30 -13
  113. package/src/plugins/network.ts +175 -159
  114. package/src/plugins/performance.ts +7 -7
  115. package/src/plugins/plugin-manager.ts +47 -36
  116. package/src/plugins/tracing.ts +43 -54
  117. package/src/reporters/base.ts +29 -23
  118. package/src/reporters/json.ts +259 -80
  119. package/src/reporters/junit.ts +1 -1
  120. package/src/sdk/lh-trace-processor.ts +1033 -0
  121. package/src/sdk/trace-metrics.ts +287 -0
  122. package/src/sdk/trace-processor.ts +147 -0
@@ -0,0 +1,287 @@
1
+ /**
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2020-present, Elastic NV
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ *
24
+ */
25
+
26
+ import { Filmstrip, TraceOutput } from '../common_types';
27
+ import type {
28
+ LHProcessedNavigation,
29
+ LHProcessedTrace,
30
+ TraceEvent,
31
+ } from './trace-processor';
32
+
33
+ export class UserTimings {
34
+ static compute(trace: LHProcessedTrace) {
35
+ const { processEvents } = trace;
36
+ const measuresMap = new Map();
37
+ const userTimings: Array<TraceOutput> = [];
38
+
39
+ for (const event of processEvents) {
40
+ const { name, ph, ts, args } = event;
41
+
42
+ if (!event.cat.includes('blink.user_timing')) {
43
+ continue;
44
+ }
45
+ // discard all browser level mark events with frame data
46
+ if (
47
+ name === 'requestStart' ||
48
+ name === 'navigationStart' ||
49
+ name === 'paintNonDefaultBackgroundColor' ||
50
+ args.frame !== undefined
51
+ ) {
52
+ continue;
53
+ }
54
+
55
+ /**
56
+ * user timing mark events falls under `R` and i or I
57
+ * measure events starts with `b` and ends with `e`
58
+ * Doc - https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit#heading=h.puwqg050lyuy
59
+ */
60
+ const phase = ph.toLowerCase();
61
+ if (phase === 'r' || phase === 'i') {
62
+ userTimings.push({
63
+ name,
64
+ type: 'mark',
65
+ start: {
66
+ us: ts,
67
+ },
68
+ });
69
+ } else if (phase === 'b') {
70
+ measuresMap.set(name, ts);
71
+ } else if (phase === 'e') {
72
+ const startTime = measuresMap.get(name);
73
+ userTimings.push({
74
+ name,
75
+ type: 'measure',
76
+ start: {
77
+ us: startTime,
78
+ },
79
+ duration: {
80
+ us: ts - startTime,
81
+ },
82
+ });
83
+ }
84
+ }
85
+ return userTimings;
86
+ }
87
+ }
88
+
89
+ export class ExperienceMetrics {
90
+ static buildMetric(name: string, timestamp?: number) {
91
+ if (!timestamp) {
92
+ return;
93
+ }
94
+ return {
95
+ name,
96
+ type: 'mark',
97
+ start: {
98
+ us: timestamp,
99
+ },
100
+ };
101
+ }
102
+
103
+ static compute(trace: LHProcessedNavigation) {
104
+ const traces: Array<TraceOutput> = [];
105
+ const { timestamps, timings, lcpInvalidated } = trace;
106
+
107
+ traces.push(this.buildMetric('navigationStart', timestamps.timeOrigin));
108
+ traces.push(
109
+ this.buildMetric('firstContentfulPaint', timestamps.firstContentfulPaint)
110
+ );
111
+ /**
112
+ * lcpInvalidated - Denotes when all of the LCP events that comes from the
113
+ * current trace are invalidated. Happens if the previous LCP candidates were
114
+ * suppressed by another event coming later during the page load
115
+ * More info - https://github.com/WICG/largest-contentful-paint/#the-last-candidate
116
+ */
117
+ !lcpInvalidated &&
118
+ traces.push(
119
+ this.buildMetric(
120
+ 'largestContentfulPaint',
121
+ timestamps.largestContentfulPaint
122
+ )
123
+ );
124
+ traces.push(
125
+ this.buildMetric('domContentLoaded', timestamps.domContentLoaded)
126
+ );
127
+ traces.push(this.buildMetric('loadEvent', timestamps.load));
128
+
129
+ /**
130
+ * Lighthouse tracer extracts these metrics in milliseconds resolution,
131
+ * we convert them to microseconds resolution to keep them in sync with
132
+ * other timings
133
+ */
134
+ const millisToMicros = (value?: number) =>
135
+ value ? value * 1000 : undefined;
136
+ const keys = [
137
+ 'firstContentfulPaint',
138
+ 'largestContentfulPaint',
139
+ 'domContentLoaded',
140
+ 'load',
141
+ ];
142
+ const values = ['fcp', 'lcp', 'dcl', 'load'];
143
+ const metrics = {};
144
+
145
+ for (let i = 0; i < keys.length; i++) {
146
+ const microSecs = millisToMicros(timings[keys[i]]);
147
+ if (microSecs) {
148
+ metrics[values[i]] = { us: microSecs };
149
+ }
150
+ }
151
+
152
+ return {
153
+ metrics,
154
+ traces: traces.filter(b => Boolean(b)),
155
+ };
156
+ }
157
+ }
158
+
159
+ export class CumulativeLayoutShift {
160
+ static getLayoutShiftEvents(traceEvents: Array<TraceEvent>) {
161
+ const layoutShiftEvents = [];
162
+ // Chromium will set `had_recent_input` if there was recent user input, which
163
+ // skips shift events from contributing to CLS. This results in the first few shift
164
+ // events having `had_recent_input` set to true, so ignore it for those events.
165
+ // See https://crbug.com/1094974
166
+ let ignoreHadRecentInput = true;
167
+
168
+ for (const event of traceEvents) {
169
+ // weighted_score_delta was added in chrome 90 - https://crbug.com/1173139
170
+ if (
171
+ event.name !== 'LayoutShift' ||
172
+ !event.args?.data.is_main_frame ||
173
+ !event.args?.data.weighted_score_delta
174
+ ) {
175
+ continue;
176
+ }
177
+
178
+ if (event.args.data.had_recent_input) {
179
+ if (!ignoreHadRecentInput) continue;
180
+ } else {
181
+ ignoreHadRecentInput = false;
182
+ }
183
+
184
+ layoutShiftEvents.push({
185
+ ts: event.ts,
186
+ weightedScore: event.args.data.weighted_score_delta,
187
+ });
188
+ }
189
+
190
+ return layoutShiftEvents;
191
+ }
192
+
193
+ /**
194
+ * Calculate cumulative layout shift per sesion window where each session
195
+ * windows lasts for 5 seconds since the last layoutShift event and has 1 second
196
+ * gap between them. Return the maximum score between all windows.
197
+ * More details - https://web.dev/evolving-cls/
198
+ */
199
+ static calculateScore(layoutShiftEvents) {
200
+ const gapMicroseconds = 1_000_000; // 1 seconds gap
201
+ const limitMicroseconds = 5_000_000; // 5 seconds window
202
+ let maxScore = 0;
203
+ let currentScore = 0;
204
+ let firstTimestamp = Number.NEGATIVE_INFINITY;
205
+ let prevTimestamp = Number.NEGATIVE_INFINITY;
206
+
207
+ for (const event of layoutShiftEvents) {
208
+ const currTimestamp = event.ts;
209
+ /**
210
+ * Two cases
211
+ * - When the next layout shift event happened after the session window of
212
+ * 5 seconds.
213
+ * - When the next and previous layout shift event has a gap of 1 second
214
+ *
215
+ * Then consider the layout shift event as the start of
216
+ * the current session window and reset the cumulative score.
217
+ */
218
+ if (
219
+ currTimestamp - firstTimestamp > limitMicroseconds ||
220
+ currTimestamp - prevTimestamp > gapMicroseconds
221
+ ) {
222
+ firstTimestamp = currTimestamp;
223
+ currentScore = 0;
224
+ }
225
+ prevTimestamp = currTimestamp;
226
+ currentScore += event.weightedScore;
227
+ maxScore = Math.max(maxScore, currentScore);
228
+ }
229
+
230
+ return maxScore;
231
+ }
232
+
233
+ static compute(trace: LHProcessedTrace) {
234
+ const layoutShiftEvents = this.getLayoutShiftEvents(trace.frameTreeEvents);
235
+ const traces: Array<TraceOutput> = layoutShiftEvents.map(event => {
236
+ return {
237
+ name: 'layoutShift',
238
+ type: 'mark',
239
+ start: {
240
+ us: event.ts,
241
+ },
242
+ score: event.weightedScore,
243
+ };
244
+ });
245
+ const cumulativeLayoutShiftScore = this.calculateScore(layoutShiftEvents);
246
+
247
+ return {
248
+ cls: cumulativeLayoutShiftScore,
249
+ traces,
250
+ };
251
+ }
252
+ }
253
+
254
+ export class Filmstrips {
255
+ static filterExcesssiveScreenshots(events: Array<TraceEvent>) {
256
+ const screenshotEvents = events.filter(
257
+ evt => evt.name === 'Screenshot' && evt.args?.snapshot
258
+ );
259
+ const screenshotTimestamps = screenshotEvents.map(event => event.ts);
260
+
261
+ let lastScreenshotTs = -Infinity;
262
+ return screenshotEvents.filter(evt => {
263
+ const timeSinceLastScreenshot = evt.ts - lastScreenshotTs;
264
+ const nextScreenshotTs = screenshotTimestamps.find(ts => ts > evt.ts);
265
+ const timeUntilNextScreenshot = nextScreenshotTs
266
+ ? nextScreenshotTs - evt.ts
267
+ : Infinity;
268
+ const threshold = 500 * 1000; // Throttle to ~2fps
269
+ // Keep the frame if it's been more than 500ms since the last frame we kept or the next frame won't happen for at least 500ms
270
+ const shouldKeep =
271
+ timeUntilNextScreenshot > threshold ||
272
+ timeSinceLastScreenshot > threshold;
273
+ if (shouldKeep) lastScreenshotTs = evt.ts;
274
+ return shouldKeep;
275
+ });
276
+ }
277
+
278
+ static compute(traceEvents: Array<TraceEvent>): Array<Filmstrip> {
279
+ return Filmstrips.filterExcesssiveScreenshots(traceEvents).map(event => ({
280
+ blob: event.args.snapshot,
281
+ mime: 'image/jpeg',
282
+ start: {
283
+ us: event.ts,
284
+ },
285
+ }));
286
+ }
287
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2020-present, Elastic NV
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ *
24
+ */
25
+
26
+ import LighthouseTraceProcessor from './lh-trace-processor';
27
+ import {
28
+ ExperienceMetrics,
29
+ CumulativeLayoutShift,
30
+ UserTimings,
31
+ } from './trace-metrics';
32
+ import { PerfMetrics } from '../common_types';
33
+ import { log } from '../core/logger';
34
+
35
+ const ACCEPTABLE_NAVIGATION_URL_REGEX = /^(file|https?):/;
36
+
37
+ /**
38
+ * Trace Event Format
39
+ * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU
40
+ */
41
+ export type TraceEvent = {
42
+ name: string;
43
+ // event category
44
+ cat: string;
45
+ // process id
46
+ pid: number;
47
+ // thread id
48
+ tid: number;
49
+ // event phase
50
+ ph?: string;
51
+ args?: {
52
+ snapshot?: string;
53
+ frame: string;
54
+ data: {
55
+ had_recent_input: boolean;
56
+ is_main_frame: boolean;
57
+ cumulative_score: number;
58
+ score: number;
59
+ weighted_score_delta: number;
60
+ } & Record<string, unknown>;
61
+ };
62
+ /**
63
+ * Platform specific monotonic non decreasing clock time
64
+ * https://source.chromium.org/chromium/chromium/src/+/master:base/time/time.h;l=936;bpv=0;bpt=0
65
+ *
66
+ * The tracing clock timestamp of the event. The timestamps are provided at microsecond granularity.
67
+ */
68
+ ts: number;
69
+ };
70
+
71
+ /**
72
+ * Exported data from Lighthouse trace processor
73
+ */
74
+ type LHTraceTime = {
75
+ timeOrigin: number;
76
+ firstPaint: number;
77
+ firstContentfulPaint: number;
78
+ largestContentfulPaint: number;
79
+ largestContentfulPaintAllFrames: number;
80
+ domContentLoaded: number;
81
+ load: number;
82
+ traceEnd: number;
83
+ };
84
+
85
+ export type LHProcessedNavigation = {
86
+ domContentLoadedEvt: TraceEvent;
87
+ firstContentfulPaintEvt: TraceEvent;
88
+ largestContentfulPaintEvt: TraceEvent;
89
+ loadEvt: TraceEvent;
90
+ timestamps: LHTraceTime;
91
+ timings: LHTraceTime;
92
+ lcpInvalidated: boolean;
93
+ };
94
+
95
+ export type LHProcessedTrace = {
96
+ processEvents: Array<TraceEvent>;
97
+ frameEvents: Array<TraceEvent>;
98
+ mainThreadEvents: Array<TraceEvent>;
99
+ frameTreeEvents: Array<TraceEvent>;
100
+ timeOriginEvt: TraceEvent;
101
+ timestamps: Partial<LHTraceTime>;
102
+ timings: Partial<LHTraceTime>;
103
+ };
104
+
105
+ /**
106
+ * Extends the lighthouse trace processor which extracts all the meaningful trace
107
+ * events in chronological order from the tab's process
108
+ *
109
+ * We extend Lighthouse Processor instead of calling it directy as we want to control
110
+ * what is the interest event for Navigation start and also in future we want to measure
111
+ * metrics based on multiple navigations instead of a single navigation.
112
+ */
113
+ export class TraceProcessor extends LighthouseTraceProcessor {
114
+ static override _isNavigationStartOfInterest(event) {
115
+ return (
116
+ event.name === 'navigationStart' &&
117
+ (!event.args.data ||
118
+ !event.args.data.documentLoaderURL ||
119
+ ACCEPTABLE_NAVIGATION_URL_REGEX.test(event.args.data.documentLoaderURL))
120
+ );
121
+ }
122
+
123
+ static computeTrace(traceEvents: Array<TraceEvent>) {
124
+ // Origin of the trace is based on the last navigation event
125
+ const options = {
126
+ timeOriginDeterminationMethod: 'lastNavigationStart',
127
+ };
128
+ try {
129
+ const processedTrace = this.processTrace({ traceEvents }, options);
130
+ const processedNavigation = this.processNavigation(processedTrace);
131
+ const userTiming = UserTimings.compute(processedTrace);
132
+ const { traces: expTraces, metrics } =
133
+ ExperienceMetrics.compute(processedNavigation);
134
+ const { cls, traces: layoutTraces } =
135
+ CumulativeLayoutShift.compute(processedTrace);
136
+ const perfMetrics: Partial<PerfMetrics> = { cls, ...metrics };
137
+
138
+ return {
139
+ traces: userTiming.concat(expTraces, layoutTraces),
140
+ metrics: perfMetrics,
141
+ };
142
+ } catch (e) {
143
+ log(e);
144
+ return {};
145
+ }
146
+ }
147
+ }