Add Prometheus and Grafana monitoring instructions
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
renderPrometheusMetrics,
|
||||
sendPrometheusMetrics,
|
||||
} from '../../src/server/prometheusMetrics.js';
|
||||
|
||||
const observedAt = '2026-08-08T10:00:00.000Z';
|
||||
const snapshot = {
|
||||
traffic: {
|
||||
gatewayBytes: '9007199254740993',
|
||||
proxyBytes: '3000',
|
||||
gatewayObservedAt: observedAt,
|
||||
proxyObservedAt: observedAt,
|
||||
},
|
||||
devices: [{
|
||||
id: 'dev_0011223344556677',
|
||||
alias: 'ТВ "Зал"\\основной\nэкран',
|
||||
hostname: 'tv.local',
|
||||
ip: '192.168.50.7',
|
||||
mac: '00:11:22:33:44:55',
|
||||
uploadBytes: '9007199254740993',
|
||||
downloadBytes: '100',
|
||||
trafficObservedAt: observedAt,
|
||||
proxyUploadBytes: '0',
|
||||
proxyDownloadBytes: '0',
|
||||
proxyTrafficObservedAt: null,
|
||||
}],
|
||||
};
|
||||
|
||||
test('Prometheus exposition keeps exact counters, stable identity and escaped names', () => {
|
||||
const output = renderPrometheusMetrics(snapshot);
|
||||
|
||||
assert.match(output, /# TYPE harbor_traffic_bytes_total counter/);
|
||||
assert.match(output, /harbor_traffic_bytes_total\{source="gateway"\} 9007199254740993/);
|
||||
assert.match(output, /harbor_device_info\{device_id="dev_0011223344556677",name="ТВ \\"Зал\\"\\\\основной\\nэкран",ip="192\.168\.50\.7"\} 1/);
|
||||
assert.match(output, /harbor_device_traffic_bytes_total\{device_id="dev_0011223344556677",source="gateway",direction="upload"\} 9007199254740993/);
|
||||
assert.doesNotMatch(output, /harbor_device_traffic_bytes_total\{[^\n]*name=/);
|
||||
assert.doesNotMatch(output, /harbor_device_traffic_bytes_total\{[^\n]*source="proxy"/);
|
||||
assert.doesNotMatch(output, /00:11:22:33:44:55/);
|
||||
assert.match(output, /harbor_device_traffic_last_observed_timestamp_seconds\{device_id="dev_0011223344556677",source="gateway"\} 1786183200/);
|
||||
assert.equal(output.endsWith('\n'), true);
|
||||
});
|
||||
|
||||
test('Prometheus response uses the negotiated legacy text contract without mutating the snapshot', () => {
|
||||
const before = structuredClone(snapshot);
|
||||
const response = {
|
||||
writeHead(status, headers) {
|
||||
this.status = status;
|
||||
this.headers = headers;
|
||||
},
|
||||
end(body) {
|
||||
this.body = body;
|
||||
},
|
||||
};
|
||||
|
||||
sendPrometheusMetrics(response, snapshot);
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(response.headers['content-type'], 'text/plain; version=0.0.4; charset=utf-8');
|
||||
assert.match(response.body, /harbor_device_info/);
|
||||
assert.deepEqual(snapshot, before);
|
||||
});
|
||||
|
||||
test('invalid canonical counters fail the scrape instead of publishing corrupt values', () => {
|
||||
const invalid = { traffic: { gatewayBytes: 'broken', proxyBytes: '0' }, devices: [] };
|
||||
assert.throws(
|
||||
() => renderPrometheusMetrics(invalid),
|
||||
/Invalid Prometheus counter/,
|
||||
);
|
||||
assert.throws(
|
||||
() => sendPrometheusMetrics({ writeHead() { throw new Error('headers sent'); } }, invalid),
|
||||
/Invalid Prometheus counter/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user