Skip to content

Commit ae132d2

Browse files
feat: add WPT queue WST selected region monitoring (DELO-4838) (#97)
Optional opentelemetry integration. When `start-with-instrumentation` is used, some WPT related metrics are exported (by default on `localhost:6060/metrics` endpoint).
1 parent 1b5be14 commit ae132d2

File tree

5 files changed

+520
-4
lines changed

5 files changed

+520
-4
lines changed

instrumentation.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const opentelemetry = require('@opentelemetry/sdk-node');
2+
const {Resource} = require('@opentelemetry/resources');
3+
const {
4+
ATTR_SERVICE_NAME,
5+
ATTR_SERVICE_VERSION
6+
} = require('@opentelemetry/semantic-conventions');
7+
const {PrometheusExporter} = require('@opentelemetry/exporter-prometheus');
8+
const sdk = new opentelemetry.NodeSDK({
9+
resource: new Resource({
10+
[ATTR_SERVICE_NAME]: 'web-speed-test-server',
11+
[ATTR_SERVICE_VERSION]: require('./package.json').version
12+
}),
13+
metricReader: new PrometheusExporter({
14+
port: 6060,
15+
}),
16+
});
17+
sdk.start();

package.json

+6
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
"scripts": {
66
"test": "mocha",
77
"start": "node start.js",
8+
"start-with-instrumentation": "node --require ./instrumentation.js start.js",
89
"postinstall": "patch-package"
910
},
1011
"dependencies": {
12+
"@opentelemetry/api": "^1.9.0",
13+
"@opentelemetry/exporter-prometheus": "^0.56.0",
14+
"@opentelemetry/resources": "^1.29.0",
15+
"@opentelemetry/sdk-node": "^0.56.0",
16+
"@opentelemetry/semantic-conventions": "^1.28.0",
1117
"async": "^3.2.6",
1218
"async-mutex": "^0.5.0",
1319
"body-parser": "~1.20.3",

routes/wpt.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ const locationSelector = require("../wtp/locationSelector");
66
const logger = require('../logger').logger;
77
const {LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR, LOG_LEVEL_CRITICAL, LOG_LEVEL_DEBUG} = require('../logger');
88
const path = require('path');
9+
const opentelemetry = require('@opentelemetry/api');
10+
11+
const WstMeter = opentelemetry.metrics.getMeter('default');
12+
const testrunCounter = WstMeter.createCounter('testrun.total');
913

1014
const routeCallback = (error, result, res, rollBarMsg) => {
1115
if (error) {
@@ -52,11 +56,11 @@ const wtp = (app) => {
5256
});
5357
});
5458

55-
5659
app.post('/test/run', (req, res) => {
5760
let rollBarMsg = {testId: "N/A", thirdPartyErrorCode: "", file: path.basename((__filename))};
5861
if (!req.body) {
5962
logger.error('Could not run test missing request body', rollBarMsg, req);
63+
testrunCounter.add(1, {"status": "BAD_REQUEST"});
6064
routeCallback({statusCode: 400}, null, res, rollBarMsg);
6165
return;
6266
}
@@ -65,6 +69,7 @@ const wtp = (app) => {
6569
rollBarMsg.analyzedUrl = testUrl;
6670
if (!testUrl) {
6771
logger.error('Could not run test missing test url',rollBarMsg, req);
72+
testrunCounter.add(1, {"status": "BAD_REQUEST"});
6873
routeCallback({statusCode: 400}, null, res, rollBarMsg);
6974
return;
7075
}
@@ -75,6 +80,7 @@ const wtp = (app) => {
7580
}*/
7681
logger.info('Started test called from webspeedtest', rollBarMsg, req);
7782
apiCaller.runWtpTest(testUrl, mobile, (error, result, response, rollBarMsg) => {
83+
testrunCounter.add(1, {"status": error ? "FAILURE" : "OK"});
7884
routeCallback(error, result, res, rollBarMsg)
7985
});
8086
});

wtp/locationSelector.js

+39
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,29 @@ const config = require('config');
33
const {Mutex, withTimeout, E_TIMEOUT} = require('async-mutex');
44
const apiKeys = require('./apiKey');
55
const path = require("path");
6+
const opentelemetry = require("@opentelemetry/api");
67
const logger = require('../logger').logger;
78

89
const GET_LOCATIONS = 'http://www.webpagetest.org/getLocations.php?f=json';
910

11+
const WstMeter = opentelemetry.metrics.getMeter('default');
12+
const locationMetrics = {
13+
total: WstMeter.createGauge('location.total'),
14+
lastUpdate: WstMeter.createGauge('location.last_update'),
15+
ratio: WstMeter.createGauge('location.ratio'),
16+
score: WstMeter.createGauge('location.score'),
17+
selected: WstMeter.createGauge('location.selected'),
18+
agent: {
19+
total: WstMeter.createGauge('location.agent.total'),
20+
idle: WstMeter.createGauge('location.agent.idle'),
21+
queued: WstMeter.createGauge('location.agent.queued'),
22+
highprio: WstMeter.createGauge('location.agent.highprio'),
23+
lowprio: WstMeter.createGauge('location.agent.lowprio'),
24+
testing: WstMeter.createGauge('location.agent.testing'),
25+
blocking: WstMeter.createGauge('location.agent.blocking')
26+
},
27+
};
28+
1029
class LocationSelector {
1130
constructor() {
1231
if (!LocationSelector.instance) {
@@ -111,6 +130,26 @@ class LocationSelector {
111130
this.location = this.getBestLocationId(filtered);
112131
this.cachedAllLocations = filtered;
113132
this.lastUpdated = Date.now();
133+
134+
// telemetry
135+
locationMetrics.total.record(this.cachedAllLocations.length);
136+
locationMetrics.lastUpdate.record(this.lastUpdated);
137+
this.cachedAllLocations.forEach((loc) => {
138+
let labels = {location: loc.location};
139+
140+
locationMetrics.ratio.record(loc.PendingTests.TestAgentRatio, labels);
141+
locationMetrics.score.record(loc.score, labels);
142+
143+
locationMetrics.selected.record(loc.location === this.location ? 1 : 0, labels);
144+
145+
locationMetrics.agent.total.record(loc.PendingTests.Total, labels);
146+
locationMetrics.agent.idle.record(loc.PendingTests.Idle, labels);
147+
locationMetrics.agent.queued.record(loc.PendingTests.Queued, labels);
148+
locationMetrics.agent.highprio.record(loc.PendingTests.HighPriority, labels);
149+
locationMetrics.agent.lowprio.record(loc.PendingTests.LowPriority, labels);
150+
locationMetrics.agent.testing.record(loc.PendingTests.Testing, labels);
151+
locationMetrics.agent.blocking.record(loc.PendingTests.Blocking, labels);
152+
});
114153
};
115154

116155
async getLocation() {

0 commit comments

Comments
 (0)