184 lines
4.8 KiB
JavaScript
184 lines
4.8 KiB
JavaScript
/**
|
|
* Apache mod_status collector (service plugin).
|
|
*
|
|
* Enable: PEARDATA_APACHE=1
|
|
* URL: PEARDATA_APACHE_URL=http://127.0.0.1/server-status?auto
|
|
*
|
|
* Charts: apache.workers, apache.accesses, apache.traffic
|
|
*/
|
|
import http from 'http'
|
|
import https from 'https'
|
|
import { CollectorPlugin } from './plugin.js'
|
|
import { registerChart } from '../../../shared/metrics.js'
|
|
import logger from '../../utils/logger.js'
|
|
|
|
const log = logger.child('apache')
|
|
|
|
const CHART_WORKERS = {
|
|
id: 'apache.workers',
|
|
name: 'apache.workers',
|
|
context: 'apache.workers',
|
|
title: 'Apache workers',
|
|
units: 'workers',
|
|
family: 'apache',
|
|
chartType: 'line',
|
|
priority: 8400,
|
|
plugin: 'apache',
|
|
dimensions: [
|
|
{ id: 'BusyWorkers', name: 'BusyWorkers', algorithm: 'absolute' },
|
|
{ id: 'IdleWorkers', name: 'IdleWorkers', algorithm: 'absolute' },
|
|
],
|
|
}
|
|
|
|
const CHART_ACCESSES = {
|
|
id: 'apache.accesses',
|
|
name: 'apache.accesses',
|
|
context: 'apache.accesses',
|
|
title: 'Apache total accesses',
|
|
units: 'accesses/s',
|
|
family: 'apache',
|
|
chartType: 'line',
|
|
priority: 8410,
|
|
plugin: 'apache',
|
|
dimensions: [{ id: 'Total_Accesses', name: 'Total Accesses', algorithm: 'absolute' }],
|
|
}
|
|
|
|
const CHART_TRAFFIC = {
|
|
id: 'apache.traffic',
|
|
name: 'apache.traffic',
|
|
context: 'apache.traffic',
|
|
title: 'Apache traffic',
|
|
units: 'KiB/s',
|
|
family: 'apache',
|
|
chartType: 'line',
|
|
priority: 8420,
|
|
plugin: 'apache',
|
|
dimensions: [{ id: 'Total_kBytes', name: 'Total kBytes', algorithm: 'absolute' }],
|
|
}
|
|
|
|
export function isApacheEnabled() {
|
|
const v = process.env.PEARDATA_APACHE
|
|
return v === '1' || v === 'on' || v === 'true'
|
|
}
|
|
|
|
/**
|
|
* Parse Apache server-status?auto body.
|
|
* @param {string} body
|
|
* @returns {{ BusyWorkers: number, IdleWorkers: number, TotalAccesses: number, TotalKBytes: number }|null}
|
|
*/
|
|
export function parseApacheServerStatus(body) {
|
|
const text = String(body || '')
|
|
const busy = text.match(/BusyWorkers:\s*(\d+)/i)
|
|
const idle = text.match(/IdleWorkers:\s*(\d+)/i)
|
|
const accesses = text.match(/Total Accesses:\s*(\d+)/i)
|
|
const kbytes = text.match(/Total kBytes:\s*(\d+)/i)
|
|
if (!busy || !idle) return null
|
|
return {
|
|
BusyWorkers: Number(busy[1]),
|
|
IdleWorkers: Number(idle[1]),
|
|
TotalAccesses: accesses ? Number(accesses[1]) : 0,
|
|
TotalKBytes: kbytes ? Number(kbytes[1]) : 0,
|
|
}
|
|
}
|
|
|
|
function fetchText(url, timeoutMs = 3000) {
|
|
return new Promise((resolve, reject) => {
|
|
const mod = String(url).startsWith('https') ? https : http
|
|
const req = mod.get(url, (res) => {
|
|
let body = ''
|
|
res.on('data', (c) => {
|
|
body += c
|
|
})
|
|
res.on('end', () => {
|
|
if (res.statusCode && res.statusCode >= 400) {
|
|
reject(new Error(`HTTP ${res.statusCode}`))
|
|
return
|
|
}
|
|
resolve(body)
|
|
})
|
|
})
|
|
req.setTimeout(timeoutMs, () => {
|
|
req.destroy()
|
|
reject(new Error('timeout'))
|
|
})
|
|
req.on('error', reject)
|
|
})
|
|
}
|
|
|
|
export class ApacheCollector extends CollectorPlugin {
|
|
constructor(opts = {}) {
|
|
super({ name: 'apache', intervalMs: opts.intervalMs })
|
|
this.url =
|
|
opts.url || process.env.PEARDATA_APACHE_URL || 'http://127.0.0.1/server-status?auto'
|
|
/** @type {{ accesses: number, kbytes: number, wallMs: number }|null} */
|
|
this._prev = null
|
|
}
|
|
|
|
isEnabled() {
|
|
return isApacheEnabled()
|
|
}
|
|
|
|
start() {
|
|
if (!this.isEnabled()) return
|
|
registerChart(CHART_WORKERS)
|
|
registerChart(CHART_ACCESSES)
|
|
registerChart(CHART_TRAFFIC)
|
|
log.info('Apache collector started', { url: this.url })
|
|
super.start()
|
|
}
|
|
|
|
async collect() {
|
|
const body = await fetchText(this.url)
|
|
const parsed = parseApacheServerStatus(body)
|
|
if (!parsed) throw new Error('unrecognized server-status body')
|
|
const ts = Date.now()
|
|
|
|
let accessRate = 0
|
|
let trafficRate = 0
|
|
if (this._prev && ts > this._prev.wallMs) {
|
|
const dt = (ts - this._prev.wallMs) / 1000
|
|
if (dt > 0) {
|
|
accessRate = Math.max(0, (parsed.TotalAccesses - this._prev.accesses) / dt)
|
|
trafficRate = Math.max(0, (parsed.TotalKBytes - this._prev.kbytes) / dt)
|
|
}
|
|
}
|
|
this._prev = {
|
|
accesses: parsed.TotalAccesses,
|
|
kbytes: parsed.TotalKBytes,
|
|
wallMs: ts,
|
|
}
|
|
|
|
return [
|
|
{
|
|
chart: 'apache.workers',
|
|
context: 'apache.workers',
|
|
ts,
|
|
values: {
|
|
BusyWorkers: parsed.BusyWorkers,
|
|
IdleWorkers: parsed.IdleWorkers,
|
|
},
|
|
},
|
|
{
|
|
chart: 'apache.accesses',
|
|
context: 'apache.accesses',
|
|
ts,
|
|
values: { Total_Accesses: accessRate },
|
|
},
|
|
{
|
|
chart: 'apache.traffic',
|
|
context: 'apache.traffic',
|
|
ts,
|
|
values: { Total_kBytes: trafficRate },
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
/** @type {ApacheCollector|null} */
|
|
let singleton = null
|
|
|
|
export function getApacheCollector() {
|
|
if (!singleton) singleton = new ApacheCollector()
|
|
return singleton
|
|
}
|