openvibers/openvibe.host Commits

Metrics collection (Track O): scripts/prometheus-config.js renders a Prometheus scrape config from the host inventory (one loopback job per service port, declared extra processes such as the Tools satellites, node-exporter; metrics:false opts out); inventory example declares Tools' seven satellites and Sites' opt-out

OP OpenVibers committed 24 Sept 2026 · 6ff101ee18 · 3 files, +82 −0


Changes

ddiff --git a/host.example.json b/host.example.json
iindex c0dbbcd..843a9f2 100644
---- a/host.example.json
++++ b/host.example.json
@@@ -171,6 +171,7 @@
],
"envFile": "/etc/openvibe/tools.env",
"env": { "required": ["OV_NETWORK_URL", "OV_OAUTH_CLIENT_ID", "OV_OAUTH_CLIENT_SECRET", "MEDIA_URL"] },
++ "metrics": { "extra": [{ "name": "maps", "port": 4010 }, { "name": "food", "port": 4011 }, { "name": "img", "port": 4012 }, { "name": "yt", "port": 4013 }, { "name": "audio", "port": 4014 }, { "name": "text", "port": 4015 }, { "name": "docs", "port": 4016 }] },
"port": 4001,
"ready": { "url": "http://127.0.0.1:4001/api/ready", "headers": { "Host": "openvibe.tools" }, "timeoutSeconds": 60 },
"noRestartPaths": ["**/*.md"],
@@@ -308,6 +309,7 @@
}
},
"sites": {
++ "metrics": false,
"repo": "/opt/openvibe.sites",
"units": [],
"install": { "command": ["npm", "ci", "--omit=dev", "--no-audit", "--no-fund"], "always": true },
ddiff --git a/scripts/prometheus-config.js b/scripts/prometheus-config.js
nnew file mode 100644
iindex 0000000..608df74
---- /dev/null
++++ b/scripts/prometheus-config.js
@@@ -0,0 +1,60 @@
++#!/usr/bin/env node
++'use strict';
++/**
++ * Prometheus scrape configuration from the host inventory (roadmap Track O): one job per service
++ * with a port, scraping its loopback /metrics, plus the extra targets a service declares:
++ *
++ * "tools": { …, "metrics": { "extra": [{ "name": "img", "port": 4012 }, …] } } more processes
++ * "sites": { …, "metrics": false } nothing to scrape
++ *
++ * Every target is 127.0.0.1 (the services answer /metrics only to direct loopback callers), and
++ * node-exporter (host CPU, memory, disk) is scraped on 127.0.0.1:9100.
++ *
++ * node scripts/prometheus-config.js [--inventory /etc/openvibe/host.json] [--interval 30s] > prometheus.yml
++ */
++const fs = require('fs');
++
++function arg(name, fallback) {
++ const i = process.argv.indexOf(`--${name}`);
++ return i > 0 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
++}
++
++function render(inventory, { interval = '30s' } = {}) {
++ const jobs = [];
++ const services = inventory.services || {};
++ for (const id of Object.keys(services).sort()) {
++ const s = services[id];
++ if (s.metrics === false) continue;
++ const m = s.metrics && typeof s.metrics === 'object' ? s.metrics : {};
++ const path = m.path || '/metrics';
++ const targets = [];
++ if (Number.isInteger(s.port)) targets.push({ port: s.port, process: id });
++ for (const x of Array.isArray(m.extra) ? m.extra : []) {
++ if (Number.isInteger(x.port) && /^[a-z][a-z0-9-]*$/.test(String(x.name || ''))) targets.push({ port: x.port, process: `${id}-${x.name}` });
++ }
++ if (!targets.length) continue;
++ jobs.push({ id, path, targets });
++ }
++ const lines = [
++ '# Generated by OpenVibe.Host scripts/prometheus-config.js from the host inventory. Do not edit by hand.',
++ 'global:',
++ ` scrape_interval: ${interval}`,
++ ' scrape_timeout: 10s',
++ 'scrape_configs:',
++ ' - job_name: node',
++ ' static_configs:',
++ " - targets: ['127.0.0.1:9100']",
++ ];
++ for (const j of jobs) {
++ lines.push(` - job_name: ${j.id}`, ` metrics_path: ${j.path}`, ' static_configs:');
++ for (const t of j.targets) lines.push(` - targets: ['127.0.0.1:${t.port}']`, ' labels:', ` service: ${j.id}`, ` process: ${t.process}`);
++ }
++ return `${lines.join('\n')}\n`;
++}
++
++if (require.main === module) {
++ const inv = JSON.parse(fs.readFileSync(arg('inventory', process.env.OVHOST_INVENTORY || '/etc/openvibe/host.json'), 'utf8'));
++ process.stdout.write(render(inv, { interval: arg('interval', '30s') }));
++}
++
++module.exports = { render };
ddiff --git a/test/prometheus-config.test.js b/test/prometheus-config.test.js
nnew file mode 100644
iindex 0000000..92e019d
---- /dev/null
++++ b/test/prometheus-config.test.js
@@@ -0,0 +1,20 @@
++'use strict';
++// scripts/prometheus-config.js: one loopback job per service port, declared extra processes, opt-out.
++const assert = require('assert');
++const { render } = require('../scripts/prometheus-config');
++
++const out = render({ services: {
++ live: { port: 3000 },
++ tools: { port: 4001, metrics: { extra: [{ name: 'img', port: 4012 }, { name: 'bad name', port: 1 }] } },
++ sites: { metrics: false },
++ games: { port: 8000 },
++ noport: {},
++} }, { interval: '15s' });
++assert.ok(out.includes('scrape_interval: 15s'));
++assert.ok(out.includes("- targets: ['127.0.0.1:9100']"), 'node-exporter');
++assert.ok(out.includes(" - job_name: live\n metrics_path: /metrics\n static_configs:\n - targets: ['127.0.0.1:3000']\n labels:\n service: live\n process: live\n"));
++assert.ok(out.includes(" - targets: ['127.0.0.1:4012']\n labels:\n service: tools\n process: tools-img\n"), 'declared extra process');
++assert.ok(!out.includes('bad name') && !out.includes(':1\''), 'malformed extras are skipped');
++assert.ok(!out.includes('job_name: sites') && !out.includes('job_name: noport'), 'opted out or nothing to scrape');
++assert.ok(!/0\.0\.0\.0|localhost/.test(out), 'loopback addresses only');
++console.log('prometheus config: all checks passed');