Merge pull request #687 from gnmyt/features/prometheus

🔥 Prometheus Endpunkt hinzugefügt
This commit is contained in:
Mathias Wagner
2024-05-21 19:16:53 +02:00
committed by GitHub
7 changed files with 131 additions and 0 deletions

View File

@@ -25,6 +25,8 @@ MySpeed ist eine Speedtest-Analyse-Software, welche die Geschwindigkeit deines I
- 🗄️ Füge mehrere Server direkt zu einer MySpeed-Instanz hinzu
- 🩺 Es lassen sich Healthchecks konfigurieren, welche dich bei Fehlern oder Ausfällen über E-Mail, Signal, WhatsApp oder Telegram benachrichtigen können
- 📆 Testergebnisse lassen sich bis zu 30 Tage lang speichern
- 🔥 Unterstützung für Prometheus und Grafana
- 🗳️ Wähle zwischen Ookla, LibreSpeed und Cloudflare Speedtest-Servern
- 💁 Erfahre mehr zu MySpeed auf unserer [Website](https://myspeed.dev)
### ⬇️ Installation

View File

@@ -25,6 +25,8 @@ MySpeed is a speed test analysis software that records your internet speed for u
- 🗄️ Add multiple servers directly to a MySpeed instance
- 🩺 Configure health checks to notify you via email, Signal, WhatsApp, or Telegram in case of errors or downtime
- 📆 Test results can be stored for up to 30 days
- 🔥 Support for Prometheus and Grafana
- 🗳️ Choose between Ookla, LibreSpeed and Cloudflare speed test servers
- 💁 Learn more about MySpeed on our [website](https://myspeed.dev)
### ⬇️ Installation

61
package-lock.json generated
View File

@@ -17,6 +17,7 @@
"express": "^4.19.2",
"mysql2": "^3.9.7",
"node-schedule": "^2.1.1",
"prom-client": "^15.1.2",
"sequelize": "^6.37.3",
"sqlite3": "^5.1.7",
"tmp": "^0.2.3"
@@ -155,6 +156,14 @@
"node": ">=10"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz",
"integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -410,6 +419,11 @@
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bintrees": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz",
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="
},
"node_modules/bl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
@@ -2504,6 +2518,18 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/prom-client": {
"version": "15.1.2",
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.2.tgz",
"integrity": "sha512-on3h1iXb04QFLLThrmVYg1SChBQ9N1c+nKAjebBjokBqipddH3uxmOUcEkTnzmJ8Jh/5TSUnUqS40i2QB2dJHQ==",
"dependencies": {
"@opentelemetry/api": "^1.4.0",
"tdigest": "^0.1.1"
},
"engines": {
"node": "^16 || ^18 || >=20"
}
},
"node_modules/promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@@ -3281,6 +3307,14 @@
"node": ">=8"
}
},
"node_modules/tdigest": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
"integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==",
"dependencies": {
"bintrees": "1.0.2"
}
},
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -3691,6 +3725,11 @@
"rimraf": "^3.0.2"
}
},
"@opentelemetry/api": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz",
"integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w=="
},
"@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -3883,6 +3922,11 @@
"file-uri-to-path": "1.0.0"
}
},
"bintrees": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz",
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="
},
"bl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
@@ -5438,6 +5482,15 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"prom-client": {
"version": "15.1.2",
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.2.tgz",
"integrity": "sha512-on3h1iXb04QFLLThrmVYg1SChBQ9N1c+nKAjebBjokBqipddH3uxmOUcEkTnzmJ8Jh/5TSUnUqS40i2QB2dJHQ==",
"requires": {
"@opentelemetry/api": "^1.4.0",
"tdigest": "^0.1.1"
}
},
"promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@@ -6001,6 +6054,14 @@
"xtend": "^4.0.0"
}
},
"tdigest": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
"integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==",
"requires": {
"bintrees": "1.0.2"
}
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",

View File

@@ -17,6 +17,7 @@
"express": "^4.19.2",
"mysql2": "^3.9.7",
"node-schedule": "^2.1.1",
"prom-client": "^15.1.2",
"sequelize": "^6.37.3",
"sqlite3": "^5.1.7",
"tmp": "^0.2.3"

View File

@@ -144,4 +144,19 @@ module.exports.removeOld = async () => {
}
});
return true;
}
module.exports.getLatest = async () => {
let latest = await tests.findOne({order: [["created", "DESC"]]});
if (latest.error === null) delete latest.error;
if (latest.resultId === null) delete latest.resultId;
return latest;
}
module.exports.getLatest = async () => {
let latest = await tests.findOne({order: [["created", "DESC"]]});
if (latest === null) return undefined;
if (latest.error === null) delete latest.error;
if (latest.resultId === null) delete latest.resultId;
return latest;
}

View File

@@ -24,6 +24,7 @@ app.use("/api/storage", require('./routes/storage'));
app.use("/api/recommendations", require('./routes/recommendations'));
app.use("/api/nodes", require('./routes/nodes'));
app.use("/api/integrations", require('./routes/integrations'));
app.use("/api/prometheus", require('./routes/prometheus'));
app.use("/api*", (req, res) => res.status(404).json({message: "Route not found"}));
if (process.env.NODE_ENV === 'production') {

View File

@@ -0,0 +1,49 @@
const express = require('express');
const app = express.Router();
const testController = require('../controller/speedtests');
const promClient = require('prom-client');
const config = require('../controller/config');
const bcrypt = require('bcrypt');
const pingGauge = new promClient.Gauge({name: 'myspeed_ping', help: 'Current ping in ms'});
const downloadGauge = new promClient.Gauge({name: 'myspeed_download', help: 'Current download speed in Mbps'});
const uploadGauge = new promClient.Gauge({name: 'myspeed_upload', help: 'Current upload speed in Mbps'});
const currentServerGauge = new promClient.Gauge({name: 'myspeed_server', help: 'Current server ID',});
const timeGauge = new promClient.Gauge({name: 'myspeed_time', help: 'Time of the test'});
app.get('/metrics', async (req, res) => {
let passwordHash = await config.getValue("password");
if (passwordHash !== "none") {
if (!req.headers.authorization || req.headers.authorization.indexOf('Basic ') === -1) {
res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"');
return res.status(401).end('Unauthorized');
}
const base64Credentials = req.headers.authorization.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
const [username, password] = credentials.split(':');
if (username !== "prometheus" || !bcrypt.compareSync(password, passwordHash)) {
res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"');
return res.status(401).end('Unauthorized');
}
}
const latest = await testController.getLatest();
if (!latest) return res.status(500).end('No test found');
if (latest.error || latest.ping === -1)
return res.status(500).end('Error in the latest test');
pingGauge.set(latest.ping);
downloadGauge.set(latest.download);
uploadGauge.set(latest.upload);
currentServerGauge.set(latest.serverId);
timeGauge.set(latest.time);
res.set('Content-Type', promClient.register.contentType);
res.end(await promClient.register.metrics());
});
module.exports = app;