Updates
CI / test (push) Successful in 1m15s
Release rolling / release (push) Successful in 7m6s

This commit is contained in:
Raven Scott
2026-07-18 19:49:57 -04:00
parent 2e1a3e9b06
commit a639b3c953
19 changed files with 1705 additions and 293 deletions
+111
View File
@@ -0,0 +1,111 @@
/**
* Unbound DNS collector via unbound-control or remote HTTP stats.
* Enable: PEARDATA_UNBOUND=1
*/
import { spawnSync } from 'child_process'
import { CollectorPlugin } from './plugin.js'
import { registerChart } from '../../../shared/metrics.js'
export function isUnboundEnabled() {
const v = process.env.PEARDATA_UNBOUND
return v === '1' || v === 'on' || v === 'true'
}
export class UnboundCollector extends CollectorPlugin {
constructor() {
super({ name: 'unbound' })
this.prev = null
this.prevTs = 0
}
isEnabled() {
return isUnboundEnabled()
}
async collect() {
const ts = Date.now()
/** @type {Record<string, number>} */
const stats = {}
const http = process.env.PEARDATA_UNBOUND_URL
if (http) {
try {
const res = await fetch(http, { signal: AbortSignal.timeout(2000) })
const text = await res.text()
for (const line of text.split('\n')) {
const [k, v] = line.split('=')
if (k && v != null && Number.isFinite(Number(v))) stats[k.trim()] = Number(v)
}
} catch {
// fall through
}
}
if (!Object.keys(stats).length) {
const res = spawnSync('unbound-control', ['stats_noreset'], {
encoding: 'utf8',
timeout: 2000,
})
if (res.status === 0 && res.stdout) {
for (const line of res.stdout.split('\n')) {
const [k, v] = line.split('=')
if (k && v != null && Number.isFinite(Number(v))) stats[k.trim()] = Number(v)
}
}
}
registerChart({
id: 'unbound.up',
name: 'unbound.up',
context: 'unbound.up',
title: 'Unbound up',
units: 'boolean',
family: 'unbound',
chartType: 'line',
priority: 8900,
plugin: 'unbound',
dimensions: [{ id: 'up', name: 'up', algorithm: 'absolute' }],
})
if (!Object.keys(stats).length) {
return [{ chart: 'unbound.up', context: 'unbound.up', ts, values: { up: 0 } }]
}
const dt = this.prevTs ? (ts - this.prevTs) / 1000 : 0
const prev = this.prev
const num = (k) => stats[k] || 0
const rate = (k) => (prev && dt > 0 ? Math.max(0, num(k) - (prev[k] || 0)) / dt : 0)
registerChart({
id: 'unbound.queries',
name: 'unbound.queries',
context: 'unbound.queries',
title: 'Unbound queries',
units: 'queries/s',
family: 'unbound',
chartType: 'line',
priority: 8910,
plugin: 'unbound',
dimensions: [
{ id: 'total', name: 'total', algorithm: 'incremental' },
{ id: 'cache_hits', name: 'cache_hits', algorithm: 'incremental' },
{ id: 'cache_misses', name: 'cache_misses', algorithm: 'incremental' },
],
})
this.prev = stats
this.prevTs = ts
return [
{ chart: 'unbound.up', context: 'unbound.up', ts, values: { up: 1 } },
{
chart: 'unbound.queries',
context: 'unbound.queries',
ts,
values: {
total: rate('total.num.queries') || rate('num.queries'),
cache_hits: rate('total.num.cachehits') || rate('num.cachehits'),
cache_misses: rate('total.num.cachemiss') || rate('num.cachemiss'),
},
},
]
}
}
let singleton = null
export function getUnboundCollector() {
if (!singleton) singleton = new UnboundCollector()
return singleton
}