sitespeed.io 36.4.0 → 37.0.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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "sitespeed.io": "./bin/sitespeed.js",
6
6
  "sitespeed.io-wpr": "./bin/browsertimeWebPageReplay.js"
7
7
  },
8
- "version": "36.4.0",
8
+ "version": "37.0.0",
9
9
  "description": "sitespeed.io is an open-source tool for comprehensive web performance analysis, enabling you to test, monitor, and optimize your website’s speed using real browsers in various environments.",
10
10
  "keywords": [
11
11
  "performance",
@@ -83,20 +83,17 @@
83
83
  "dependencies": {
84
84
  "@aws-sdk/client-s3": "3.717.0",
85
85
  "@google-cloud/storage": "7.14.0",
86
- "@influxdata/influxdb-client": "1.33.2",
87
86
  "@sitespeed.io/log": "0.2.6",
88
87
  "@sitespeed.io/plugin": "1.0.0",
89
88
  "@tgwf/co2": "0.16.4",
90
89
  "@slack/webhook": "7.0.4",
91
90
  "axe-core": "4.10.2",
92
- "browsertime": "24.2.0",
91
+ "browsertime": "24.3.0",
93
92
  "coach-core": "8.1.1",
94
93
  "dayjs": "1.11.11",
95
94
  "fast-crc32c": "2.0.0",
96
95
  "fast-stats": "0.0.7",
97
96
  "import-global": "1.1.1",
98
- "influx": "5.9.3",
99
- "junit-report-builder": "3.2.1",
100
97
  "lodash.get": "4.4.2",
101
98
  "lodash.merge": "4.6.2",
102
99
  "lodash.set": "4.3.2",
@@ -105,7 +102,6 @@
105
102
  "ora": "8.0.1",
106
103
  "pug": "3.0.3",
107
104
  "simplecrawler": "1.1.9",
108
- "tape": "5.8.1",
109
105
  "yargs": "17.7.2"
110
106
  },
111
107
  "overrides": {
@@ -1,262 +0,0 @@
1
- import merge from 'lodash.merge';
2
-
3
- import { flattenMessageData } from '../../support/flattenMessage.js';
4
- import {
5
- getConnectivity,
6
- getURLAndGroup,
7
- toSafeKey
8
- } from '../../support/tsdbUtil.js';
9
-
10
- function getAdditionalTags(key, type) {
11
- let tags = {};
12
- const keyArray = key.split('.');
13
- if (/(^contentTypes)/.test(key)) {
14
- // contentTypes.favicon.requests.mean
15
- // contentTypes.favicon.requests
16
- // contentTypes.css.transferSize
17
- tags.contentType = keyArray[1];
18
- } else if (/(^pageTimings|^visualMetrics)/.test(key)) {
19
- // pageTimings.serverResponseTime.max
20
- // visualMetrics.SpeedIndex.median
21
- tags.timings = keyArray[0];
22
- } else
23
- switch (type) {
24
- case 'browsertime.pageSummary': {
25
- // statistics.timings.pageTimings.backEndTime.median
26
- // statistics.timings.userTimings.marks.logoTime.median
27
- // statistics.visualMetrics.SpeedIndex.median
28
- tags[keyArray[0]] = keyArray[1];
29
- if (keyArray.length >= 5) {
30
- tags[keyArray[2]] = keyArray[3];
31
- }
32
- if (key.includes('cpu.categories')) {
33
- tags.cpu = 'category';
34
- } else if (key.includes('cpu.events')) {
35
- tags.cpu = 'event';
36
- } else if (key.includes('cpu.longTasks')) {
37
- tags.cpu = 'longTask';
38
- }
39
-
40
- break;
41
- }
42
- case 'browsertime.summary': {
43
- // firstPaint.median
44
- // userTimings.marks.logoTime.median
45
- if (key.includes('userTimings')) {
46
- tags[keyArray[0]] = keyArray[1];
47
- }
48
-
49
- break;
50
- }
51
- case 'axe.pageSummary': {
52
- tags.axeType = keyArray[0];
53
- break;
54
- }
55
- case 'coach.pageSummary': {
56
- // advice.score
57
- // advice.performance.score
58
- if (keyArray.length > 2) {
59
- tags.advice = keyArray[1];
60
- }
61
-
62
- // set the actual advice name
63
- // advice.performance.adviceList.cacheHeaders.score
64
- if (keyArray.length > 4) {
65
- tags.adviceName = keyArray[3];
66
- }
67
-
68
- break;
69
- }
70
- case 'coach.summary': {
71
- // score.max
72
- // performance.score.median
73
- if (keyArray.length === 3) {
74
- tags.advice = keyArray[0];
75
- }
76
-
77
- break;
78
- }
79
- case 'pagexray.summary': {
80
- // firstParty.requests.min pagexray.summary
81
- // requests.median
82
- // responseCodes.307.max pagexray.summary
83
- // requests.min pagexray.summary
84
- if (key.includes('responseCodes')) {
85
- tags.responseCodes = 'response';
86
- }
87
-
88
- if (key.includes('firstParty') || key.includes('thirdParty')) {
89
- tags.party = keyArray[0];
90
- }
91
-
92
- break;
93
- }
94
- case 'pagexray.pageSummary': {
95
- // thirdParty.contentTypes.json.requests pagexray.pageSummary
96
- // thirdParty.requests pagexray.pageSummary
97
- // firstParty.cookieStats.max pagexray.pageSummary
98
- // responseCodes.200 pagexray.pageSummary
99
- // expireStats.max pagexray.pageSummary
100
- // totalDomains pagexray.pageSummary
101
- if (key.includes('firstParty') || key.includes('thirdParty')) {
102
- tags.party = keyArray[0];
103
- }
104
- if (key.includes('responseCodes')) {
105
- tags.responseCodes = 'response';
106
- }
107
- if (key.includes('contentTypes')) {
108
- tags.contentType = keyArray[2];
109
- }
110
-
111
- break;
112
- }
113
- case 'thirdparty.pageSummary': {
114
- tags.thirdPartyCategory = keyArray[1];
115
- tags.thirdPartyType = keyArray[2];
116
-
117
- break;
118
- }
119
- case 'lighthouse.pageSummary': {
120
- // categories.seo.score
121
- // categories.performance.score
122
- if (key.includes('score')) {
123
- tags.audit = keyArray[1];
124
- }
125
- if (key.includes('audits')) {
126
- tags.audit = keyArray[1];
127
- }
128
-
129
- break;
130
- }
131
- case 'crux.pageSummary': {
132
- tags.experience = keyArray[0];
133
- tags.formFactor = keyArray[1];
134
- tags.metric = keyArray[2];
135
-
136
- break;
137
- }
138
- case 'gpsi.pageSummary': {
139
- if (key.includes('googleWebVitals')) {
140
- tags.testType = 'googleWebVitals';
141
- } else if (key.includes('score')) {
142
- tags.testType = 'score';
143
- } else if (key.includes('loadingExperience')) {
144
- tags.experience = keyArray[0];
145
- tags.metric = keyArray[1];
146
- tags.testType = 'crux';
147
- }
148
-
149
- break;
150
- }
151
- default:
152
- // console.log('Missed added tags to ' + key + ' ' + type);
153
- }
154
- return tags;
155
- }
156
-
157
- function getFieldAndSeriesName(key) {
158
- const functions = [
159
- 'min',
160
- 'p10',
161
- 'median',
162
- 'mean',
163
- 'avg',
164
- 'max',
165
- 'p90',
166
- 'p99',
167
- 'mdev',
168
- 'stddev',
169
- 'rsd'
170
- ];
171
- const keyArray = key.split('.');
172
- const end = keyArray.pop();
173
- if (functions.includes(end)) {
174
- return { field: end, seriesName: keyArray.pop() };
175
- }
176
- return { field: 'value', seriesName: end };
177
- }
178
- export class InfluxDBDataGenerator {
179
- constructor(includeQueryParameters, options) {
180
- this.includeQueryParams = !!includeQueryParameters;
181
- this.options = options;
182
- this.defaultTags = {};
183
- for (let row of options.influxdb.tags.split(',')) {
184
- const keyAndValue = row.split('=');
185
- this.defaultTags[keyAndValue[0]] = keyAndValue[1];
186
- }
187
- }
188
-
189
- dataFromMessage(message, time, alias) {
190
- function getTagsFromMessage(
191
- message,
192
- includeQueryParameters,
193
- options,
194
- defaultTags
195
- ) {
196
- const tags = merge({}, defaultTags);
197
- let typeParts = message.type.split('.');
198
- tags.origin = typeParts[0];
199
- typeParts.push(typeParts.shift());
200
- tags.summaryType = typeParts[0];
201
-
202
- // always have browser and connectivity in Browsertime and related tools
203
- if (
204
- /(^pagexray|^coach|^browsertime|^thirdparty|^axe|^sustainable)/.test(
205
- message.type
206
- )
207
- ) {
208
- // if we have a friendly name for your connectivity, use that!
209
- let connectivity = getConnectivity(options);
210
- tags.connectivity = connectivity;
211
- tags.browser = options.browser;
212
- } else if (/(^gpsi)/.test(message.type)) {
213
- tags.strategy = options.mobile ? 'mobile' : 'desktop';
214
- }
215
-
216
- // if we get a URL type, add the URL
217
- if (message.url) {
218
- const urlAndGroup = getURLAndGroup(
219
- options,
220
- message.group,
221
- message.url,
222
- includeQueryParameters,
223
- alias
224
- ).split('.');
225
- tags.page = urlAndGroup[1];
226
- tags.group = urlAndGroup[0];
227
- } else if (message.group) {
228
- // add the group of the summary message
229
- tags.group = toSafeKey(message.group, options.influxdb.groupSeparator);
230
- }
231
-
232
- if (options.slug) {
233
- tags.testName = options.slug;
234
- }
235
-
236
- return tags;
237
- }
238
- return Object.entries(flattenMessageData(message)).reduce(
239
- (entries, [key, value]) => {
240
- const fieldAndSeriesName = getFieldAndSeriesName(key);
241
- let tags = getTagsFromMessage(
242
- message,
243
- this.includeQueryParams,
244
- this.options,
245
- this.defaultTags
246
- );
247
- tags = { ...getAdditionalTags(key, message.type), ...tags };
248
- const point = {
249
- time: time.valueOf(),
250
- [fieldAndSeriesName.field]: value
251
- };
252
- entries.push({
253
- tags,
254
- seriesName: fieldAndSeriesName.seriesName,
255
- point
256
- });
257
- return entries;
258
- },
259
- []
260
- );
261
- }
262
- }
@@ -1,176 +0,0 @@
1
- import { getLogger } from '@sitespeed.io/log';
2
- import dayjs from 'dayjs';
3
-
4
- import { SitespeedioPlugin } from '@sitespeed.io/plugin';
5
- import { InfluxDBSender as Sender } from './sender.js';
6
- import { InfluxDB2Sender as SenderV2 } from './senderV2.js';
7
- import { sendV1 } from './send-annotation.js';
8
- import { sendV2 } from './send-annotationV2.js';
9
- import { InfluxDBDataGenerator as DataGenerator } from './data-generator.js';
10
- import { throwIfMissing, isEmpty } from '../../support/util.js';
11
-
12
- const log = getLogger('sitespeedio.plugin.influxdb');
13
- export default class InfluxDBPlugin extends SitespeedioPlugin {
14
- constructor(options, context, queue) {
15
- super({ name: 'influxdb', options, context, queue });
16
- }
17
-
18
- open(context, options) {
19
- throwIfMissing(options.influxdb, ['host', 'database'], 'influxdb');
20
- this.filterRegistry = context.filterRegistry;
21
- log.debug(
22
- 'Setup InfluxDB host %s and database %s',
23
- options.influxdb.host,
24
- options.influxdb.database
25
- );
26
-
27
- const options_ = options.influxdb;
28
- this.options = options;
29
- this.sender =
30
- options_.version == 1 ? new Sender(options_) : new SenderV2(options_);
31
- this.timestamp = context.timestamp;
32
- this.resultUrls = context.resultUrls;
33
- this.dataGenerator = new DataGenerator(
34
- options_.includeQueryParams,
35
- options
36
- );
37
- this.messageTypesToFireAnnotations = [];
38
- this.receivedTypesThatFireAnnotations = {};
39
- this.make = context.messageMaker('influxdb').make;
40
- this.sendAnnotation = true;
41
- this.alias = {};
42
- }
43
-
44
- processMessage(message, queue) {
45
- const filterRegistry = this.filterRegistry;
46
-
47
- // First catch if we are running Browsertime and/or WebPageTest
48
- switch (message.type) {
49
- case 'browsertime.setup': {
50
- this.messageTypesToFireAnnotations.push('browsertime.pageSummary');
51
- this.usingBrowsertime = true;
52
-
53
- break;
54
- }
55
- case 'browsertime.config': {
56
- if (message.data.screenshot) {
57
- this.useScreenshots = message.data.screenshot;
58
- this.screenshotType = message.data.screenshotType;
59
- }
60
-
61
- break;
62
- }
63
- case 'sitespeedio.setup': {
64
- // Let other plugins know that the InfluxDB plugin is alive
65
- queue.postMessage(this.make('influxdb.setup'));
66
-
67
- break;
68
- }
69
- case 'grafana.setup': {
70
- this.sendAnnotation = false;
71
-
72
- break;
73
- }
74
- // No default
75
- }
76
-
77
- if (message.type === 'browsertime.alias') {
78
- this.alias[message.url] = message.data;
79
- }
80
-
81
- if (
82
- !(
83
- message.type.endsWith('.summary') ||
84
- message.type.endsWith('.pageSummary')
85
- )
86
- ) {
87
- return;
88
- }
89
-
90
- if (this.messageTypesToFireAnnotations.includes(message.type)) {
91
- this.receivedTypesThatFireAnnotations[message.url]
92
- ? this.receivedTypesThatFireAnnotations[message.url]++
93
- : (this.receivedTypesThatFireAnnotations[message.url] = 1);
94
- }
95
-
96
- // Let us skip this for a while and concentrate on the real deal
97
- if (
98
- /(^largestassets|^slowestassets|^aggregateassets|^domains)/.test(
99
- message.type
100
- )
101
- )
102
- return;
103
-
104
- // we only sends individual groups to Influx, not the
105
- // total of all groups (you can calculate that yourself)
106
- if (message.group === 'total') {
107
- return;
108
- }
109
-
110
- message = filterRegistry.filterMessage(message);
111
- if (isEmpty(message.data)) return;
112
-
113
- let data = this.dataGenerator.dataFromMessage(
114
- message,
115
- message.type === 'browsertime.pageSummary'
116
- ? dayjs(message.runTime)
117
- : this.timestamp,
118
- this.alias
119
- );
120
-
121
- if (data.length > 0) {
122
- log.debug('Send the following data to InfluxDB: %:2j', data);
123
- return this.sender.send(data).then(() => {
124
- // Make sure we only send the annotation once per URL:
125
- // If we run browsertime, always send on browsertime.pageSummary
126
- // If we run WebPageTest standalone, send on webPageTestSummary
127
- // when we configured a base url
128
- if (
129
- this.receivedTypesThatFireAnnotations[message.url] ===
130
- this.messageTypesToFireAnnotations.length &&
131
- this.resultUrls.hasBaseUrl() &&
132
- this.sendAnnotation
133
- ) {
134
- const absolutePagePath = this.resultUrls.absoluteSummaryPagePath(
135
- message.url,
136
- this.alias[message.url]
137
- );
138
- this.receivedTypesThatFireAnnotations[message.url] = 0;
139
-
140
- return this.options.influxdb.version == 2
141
- ? sendV2(
142
- message.url,
143
- message.group,
144
- absolutePagePath,
145
- this.useScreenshots,
146
- this.screenshotType,
147
- // Browsertime pass on when the first run was done for that URL
148
- message.runTime,
149
- this.alias,
150
- this.usingBrowsertime,
151
- this.options
152
- )
153
- : sendV1(
154
- message.url,
155
- message.group,
156
- absolutePagePath,
157
- this.useScreenshots,
158
- this.screenshotType,
159
- // Browsertime pass on when the first run was done for that URL
160
- message.runTime,
161
- this.alias,
162
- this.usingBrowsertime,
163
- this.options
164
- );
165
- }
166
- });
167
- } else {
168
- return Promise.reject(
169
- new Error(
170
- 'No data to send to influxdb for message:\n' +
171
- JSON.stringify(message, undefined, 2)
172
- )
173
- );
174
- }
175
- }
176
- }
@@ -1,111 +0,0 @@
1
- import http from 'node:http';
2
- import https from 'node:https';
3
- import { stringify } from 'node:querystring';
4
-
5
- import { getLogger } from '@sitespeed.io/log';
6
- import dayjs from 'dayjs';
7
-
8
- import { getConnectivity, getURLAndGroup } from '../../support/tsdbUtil.js';
9
- import {
10
- getAnnotationMessage,
11
- getTagsAsString
12
- } from '../../support/annotationsHelper.js';
13
-
14
- const log = getLogger('sitespeedio.plugin.influxdb');
15
-
16
- export function sendV1(
17
- url,
18
- group,
19
- absolutePagePath,
20
- screenShotsEnabledInBrowsertime,
21
- screenshotType,
22
- runTime,
23
- alias,
24
- usingBrowsertime,
25
- options
26
- ) {
27
- // The tags make it possible for the dashboard to use the
28
- // templates to choose which annotations that will be showed.
29
- // That's why we need to send tags that matches the template
30
- // variables in Grafana.
31
- const connectivity = getConnectivity(options);
32
- const browser = options.browser;
33
- const urlAndGroup = getURLAndGroup(
34
- options,
35
- group,
36
- url,
37
- options.influxdb.includeQueryParams,
38
- alias
39
- ).split('.');
40
- let tags = [connectivity, browser, urlAndGroup[0], urlAndGroup[1]];
41
-
42
- if (options.slug) {
43
- tags.push(options.slug);
44
- }
45
-
46
- const message = getAnnotationMessage(
47
- absolutePagePath,
48
- screenShotsEnabledInBrowsertime,
49
- screenshotType,
50
- undefined,
51
- usingBrowsertime,
52
- options
53
- );
54
- const timestamp = runTime
55
- ? Math.round(dayjs(runTime) / 1000)
56
- : Math.round(dayjs() / 1000);
57
- // if we have a category, let us send that category too
58
- if (options.influxdb.tags) {
59
- for (let row of options.influxdb.tags.split(',')) {
60
- const keyAndValue = row.split('=');
61
- tags.push(keyAndValue[1]);
62
- }
63
- }
64
- const influxDBTags = getTagsAsString(tags);
65
- const postData = `events title="Sitespeed.io",text="${message}",tags=${influxDBTags} ${timestamp}`;
66
- const postOptions = {
67
- hostname: options.influxdb.host,
68
- port: options.influxdb.port,
69
- path: '/write?db=' + options.influxdb.database + '&precision=s',
70
- method: 'POST',
71
- headers: {
72
- 'Content-Type': 'application/x-www-form-urlencoded',
73
- 'Content-Length': Buffer.byteLength(postData)
74
- }
75
- };
76
-
77
- if (options.influxdb.username) {
78
- postOptions.path =
79
- postOptions.path +
80
- '&' +
81
- stringify({
82
- u: options.influxdb.username,
83
- p: options.influxdb.password
84
- });
85
- }
86
-
87
- return new Promise((resolve, reject) => {
88
- log.debug('Send annotation to Influx: %j', postData);
89
- // not perfect but maybe work for us
90
- const library = options.influxdb.protocol === 'https' ? https : http;
91
- const request = library.request(postOptions, res => {
92
- if (res.statusCode === 204) {
93
- res.setEncoding('utf8');
94
- log.debug('Sent annotation to InfluxDB');
95
- resolve();
96
- } else {
97
- const e = new Error(
98
- `Got ${res.statusCode} from InfluxDB when sending annotation ${res.statusMessage}`
99
- );
100
- log.warn(e.message);
101
- reject(e);
102
- }
103
- });
104
- request.on('error', error => {
105
- log.error('Got error from InfluxDB when sending annotation', error);
106
- reject(error);
107
- });
108
- request.write(postData);
109
- request.end();
110
- });
111
- }
@@ -1,106 +0,0 @@
1
- import http from 'node:http';
2
- import https from 'node:https';
3
-
4
- import { getLogger } from '@sitespeed.io/log';
5
- import dayjs from 'dayjs';
6
-
7
- import { getConnectivity, getURLAndGroup } from '../../support/tsdbUtil.js';
8
- import {
9
- getAnnotationMessage,
10
- getTagsAsString
11
- } from '../../support/annotationsHelper.js';
12
-
13
- const log = getLogger('sitespeedio.plugin.influxdb');
14
-
15
- export function sendV2(
16
- url,
17
- group,
18
- absolutePagePath,
19
- screenShotsEnabledInBrowsertime,
20
- screenshotType,
21
- runTime,
22
- alias,
23
- usingBrowsertime,
24
- options
25
- ) {
26
- // The tags make it possible for the dashboard to use the
27
- // templates to choose which annotations that will be showed.
28
- // That's why we need to send tags that matches the template
29
- // variables in Grafana.
30
- const connectivity = getConnectivity(options);
31
- const browser = options.browser;
32
- const urlAndGroup = getURLAndGroup(
33
- options,
34
- group,
35
- url,
36
- options.influxdb.includeQueryParams,
37
- alias
38
- ).split('.');
39
- let tags = [
40
- `connectivity=${connectivity}`,
41
- `browser=${browser}`,
42
- `group=${urlAndGroup[0]}`,
43
- `page=${urlAndGroup[1]}`
44
- ];
45
-
46
- if (options.slug) {
47
- tags.push(`slug=${options.slug}`);
48
- }
49
-
50
- const message = getAnnotationMessage(
51
- absolutePagePath,
52
- screenShotsEnabledInBrowsertime,
53
- screenshotType,
54
- undefined,
55
- usingBrowsertime,
56
- options
57
- );
58
- const timestamp = runTime
59
- ? Math.round(dayjs(runTime) / 1000)
60
- : Math.round(dayjs() / 1000);
61
- // if we have a category, let us send that category too
62
- if (options.influxdb.tags) {
63
- for (const tag of options.influxdb.tags.split(',')) {
64
- tags.push(tag);
65
- }
66
- }
67
- const influxDBTags = tags.join(',');
68
- const grafanaTags = getTagsAsString(tags.map(pair => pair.split('=')[1]));
69
- const postData = `annotations,${influxDBTags} title="Sitespeed.io",text="${message}",tags=${grafanaTags} ${timestamp}`;
70
- const postOptions = {
71
- hostname: options.influxdb.host,
72
- port: options.influxdb.port,
73
- path: `/api/v2/write?org=${options.influxdb.organisation}&bucket=${options.influxdb.database}&precision=s`,
74
- method: 'POST',
75
- headers: {
76
- 'Content-Type': 'application/x-www-form-urlencoded',
77
- 'Content-Length': Buffer.byteLength(postData),
78
- Authorization: `Token ${options.influxdb.token}`
79
- }
80
- };
81
-
82
- return new Promise((resolve, reject) => {
83
- log.debug('Send annotation to Influx: %j', postData);
84
- // not perfect but maybe work for us
85
- const library = options.influxdb.protocol === 'https' ? https : http;
86
- const request = library.request(postOptions, res => {
87
- if (res.statusCode === 204) {
88
- res.setEncoding('utf8');
89
- log.debug('Sent annotation to InfluxDB');
90
- resolve();
91
- } else {
92
- const e = new Error(
93
- `Got ${res.statusCode} from InfluxDB when sending annotation ${res.statusMessage}`
94
- );
95
- log.warn(e.message);
96
- reject(e);
97
- }
98
- });
99
- request.on('error', error => {
100
- log.error('Got error from InfluxDB when sending annotation', error);
101
- reject(error);
102
- });
103
- request.write(postData);
104
- request.end();
105
- });
106
- }