113 lines
3.3 KiB
JavaScript
113 lines
3.3 KiB
JavaScript
/**
|
|
* Unbound DNS collector via unbound-control or remote HTTP stats.
|
|
* Enable: PEARDATA_UNBOUND=1
|
|
*/
|
|
import { CollectorPlugin } from './plugin.js'
|
|
import { registerChart } from '../../../shared/metrics.js'
|
|
import { execFileSync } from '../../utils/exec.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) {
|
|
try {
|
|
const { stdout } = execFileSync('unbound-control', ['stats_noreset'], {
|
|
encoding: 'utf8',
|
|
})
|
|
for (const line of String(stdout || '').split('\n')) {
|
|
const [k, v] = line.split('=')
|
|
if (k && v != null && Number.isFinite(Number(v))) stats[k.trim()] = Number(v)
|
|
}
|
|
} catch {
|
|
// unbound-control unavailable
|
|
}
|
|
}
|
|
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
|
|
}
|