1import express from 'express';
2import * as testController from '../controller/speedtests.js';
3import promClient from 'prom-client';
4import * as config from '../controller/config.js';
5import * as serverController from '../controller/servers.js';
6import bcrypt from 'bcryptjs';
7
8const app = express.Router();
9
10const speedLabels = ['server_id', 'server_name', 'server_host'];
11
12const pingGauge = new promClient.Gauge({name: 'pixies_ping', help: 'Current ping in ms', labelNames: speedLabels});
13const jitterGauge = new promClient.Gauge({name: 'pixies_jitter', help: 'Current jitter in ms', labelNames: speedLabels});
14const downloadGauge = new promClient.Gauge({name: 'pixies_download', help: 'Current download speed in Mbps', labelNames: speedLabels});
15const uploadGauge = new promClient.Gauge({name: 'pixies_upload', help: 'Current upload speed in Mbps', labelNames: speedLabels});
16const currentServerGauge = new promClient.Gauge({name: 'pixies_server', help: 'Current server ID'});
17const timeGauge = new promClient.Gauge({name: 'pixies_time', help: 'Time of the test', labelNames: speedLabels});
18const serverInfoGauge = new promClient.Gauge({
19 name: 'pixies_server_info',
20 help: 'Static info about the speedtest server (always 1). Join via group_left to add server metadata to other metrics.',
21 labelNames: speedLabels
22});
23
24const resolveServerLabels = (latest) => {
25 const serverId = latest.serverId ?? 0;
26 let serverName = latest.serverName ?? null;
27 let serverHost = latest.serverHost ?? null;
28
29 if (!serverName || !serverHost) {
30 const ooklaServers = serverController.getOoklaServers();
31 const entry = ooklaServers && ooklaServers[serverId];
32 if (entry) {
33 if (typeof entry === "string") {
34 if (!serverName) serverName = entry;
35 } else if (typeof entry === "object") {
36 if (!serverName) serverName = entry.sponsor || entry.name || null;
37 if (!serverHost) serverHost = entry.host || null;
38 }
39 }
40 }
41
42 return {
43 server_id: String(serverId),
44 server_name: serverName ?? '',
45 server_host: serverHost ?? ''
46 };
47};
48
49app.get('/metrics', async (req, res) => {
50 let passwordHash = await config.getValue("password");
51
52 if (passwordHash !== "none") {
53 if (!req.headers.authorization || req.headers.authorization.indexOf('Basic ') === -1) {
54 res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"');
55 return res.status(401).end('Unauthorized');
56 }
57
58 const base64Credentials = req.headers.authorization.split(' ')[1];
59 const credentials = Buffer.from(base64Credentials, 'base64').toString('utf8');
60 const [username, password] = credentials.split(':');
61
62 if (username !== "prometheus" || !bcrypt.compareSync(password, passwordHash)) {
63 res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"');
64 return res.status(401).end('Unauthorized');
65 }
66 }
67
68 const latest = await testController.getLatest();
69 if (!latest) return res.status(500).end('No test found');
70
71 if (latest.error || latest.ping === -1)
72 return res.status(500).end('Error in the latest test');
73
74 const labels = resolveServerLabels(latest);
75
76 pingGauge.reset();
77 jitterGauge.reset();
78 downloadGauge.reset();
79 uploadGauge.reset();
80 timeGauge.reset();
81 serverInfoGauge.reset();
82
83 pingGauge.set(labels, latest.ping);
84 if (latest.jitter !== null && latest.jitter !== undefined)
85 jitterGauge.set(labels, latest.jitter);
86 downloadGauge.set(labels, latest.download);
87 uploadGauge.set(labels, latest.upload);
88 currentServerGauge.set(latest.serverId);
89 serverInfoGauge.set(labels, 1);
90
91 if (latest.time)
92 timeGauge.set(labels, latest.time);
93
94 res.set('Content-Type', promClient.register.contentType);
95 res.end(await promClient.register.metrics());
96});
97
98export default app;