Skip to content
Open
28 changes: 28 additions & 0 deletions lib/cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,34 @@ export async function parseCommandLine() {
' to increase the level of detail.',
type: 'count'
})
/*
OpenSearch cli Options
*/

.option('opensearch.host', {
describe: 'The OpenSearch host',
group: 'OpenSearch'
})
.option('opensearch.port', {
describe: 'The OpenSearch port',
type: 'number',
default: 9200,
group: 'OpenSearch'
})
.option('opensearch.index', {
describe: 'The index name for storing metrics',
default: 'sitespeed',
group: 'OpenSearch'
})
.option('opensearch.auth.username', {
describe: 'OpenSearch authentication username',
group: 'OpenSearch'
})
.option('opensearch.auth.password', {
describe: 'OpenSearch authentication password',
group: 'OpenSearch'
})

/*
Browsertime cli options
*/
Expand Down
118 changes: 118 additions & 0 deletions lib/plugins/opensearch/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { SitespeedioPlugin } from '@sitespeed.io/plugin';
import { getLogger } from '@sitespeed.io/log';
import { Client } from '@opensearch-project/opensearch';

const log = getLogger('sitespeedio.plugin.opensearch');

export default class OpenSearchPlugin extends SitespeedioPlugin {
constructor(options, context, queue) {
super({ name: 'opensearch', options, context, queue });
}

open(context, options) {
this.opensearchOptions = options.opensearch;
this.index = this.opensearchOptions?.index;
this.options = options;
this.make = context.messageMaker('opensearch').make;
this.storageManager = context.storageManager;

// Initialize OpenSearch client connection
// Register a logger for this plugin, a unique name so we can filter the log
// And save the log for later
this.log = context.getLogger('sitespeedio.plugin.opensearch');
this.log.info('Plugin opensearch started');
this.initializeClient();
}

initializeClient() {
const {
host,
port,
protocol,
auth
} = this.opensearchOptions || {};

if (!host || !port) {
throw new Error('OpenSearch host and port must be defined');
}

this.client = new Client({
node: `${protocol}://${host}:${port}`,
auth: auth?.username
? {
username: auth.username,
password: auth.password
}
: undefined,
ssl: {
rejectUnauthorized: false
}
});

log.info(`Connected to ${protocol}://${host}:${port}`);
}

async processMessage(message, queue) {

if (message.type === 'browsertime.pageSummary') {
//log.info('Received browsertime.pageSummary message data:');
//log.info(JSON.stringify(message.data, null, 2));

try {
await this.sendMetricsToOpenSearch(message.data);
} catch (error) {
log.error('Failed to send metrics to OpenSearch', error);
}
}
}

async sendMetricsToOpenSearch(data) {

const document = this.transformMetrics(data);

try {
const response = await this.client.index({
index: this.index,
body: document,
refresh: false
});

log.info(`Indexed into ${this.index} id=${response.body?._id}`);

} catch (err) {
log.error('OpenSearch indexing failed');
log.error(JSON.stringify(err.meta?.body, null, 2));
throw err;
}
}

transformMetrics(data) {
const webVitals = data.googleWebVitals?.[0] || {};
const browserTimings =
data.browserScripts?.[0]?.timings?.pageTimings || {};

return {
timestamp: new Date().toISOString(),
url: data.info?.url || 'unknown',

lcp: webVitals.largestContentfulPaint ?? 0,
cls: webVitals.cumulativeLayoutShift ?? 0,

browserpbbackendtime: browserTimings.backEndTime ?? 0,
browserpbdomcontentloadedtime:
browserTimings.domContentLoadedTime ?? 0,
browserpbdominteractivetime:
browserTimings.domInteractiveTime ?? 0,
browserpbdomainlookuptime:
browserTimings.domainLookupTime ?? 0,
browserpbfrontEndTime:
browserTimings.frontEndTime ?? 0,
browserpbpageLoadTime:
browserTimings.pageLoadTime ?? 0,
browserpbserverConnectionTime:
browserTimings.serverConnectionTime ?? 0,
browserpbserverResponseTime:
browserTimings.serverResponseTime ?? 0
};
}
}
87 changes: 87 additions & 0 deletions lib/plugins/opensearch/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions lib/plugins/opensearch/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "sitespeed-opensearch-plugin",
"version": "1.0.0",
"description": "Custom Sitespeed.io plugin to send metrics to OpenSearch",
"type": "module",
"main": "index.js",
"keywords": [
"sitespeed",
"opensearch",
"performance",
"plugin"
],
"author": "Shivaram",
"license": "MIT",
"dependencies": {
"@opensearch-project/opensearch": "^2.8.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"dependencies": {
"@aws-sdk/client-s3": "3.911.0",
"@google-cloud/storage": "7.17.2",
"@opensearch-project/opensearch": "^2.0.0",
"@sitespeed.io/log": "1.0.0",
"@sitespeed.io/plugin": "1.0.0",
"@tgwf/co2": "0.16.9",
Expand Down
Loading
Loading