forked from rosteleset/SmartYard-Server
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added smart-yard prometheus exporter (rosteleset#80)
- Loading branch information
Showing
21 changed files
with
520 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
docker | ||
drafts | ||
node_modules | ||
docker-compose.yml | ||
.example_env | ||
.env | ||
package-lock.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
APP_NAME=SmartYard-Server/intercom | ||
APP_PORT=9191 | ||
APP_HOST=127.0.0.1 | ||
SERVICE_PREFIX=sys_intercom | ||
AUTH_ENABLED=false | ||
AUTH_USER=username | ||
AUTH_PASS=secure_password |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import 'dotenv/config' | ||
export const NODE_ENV= process.env.NODE_ENV || "production"; | ||
|
||
export const APP_NAME = process.env.APP_NAME | ||
export const APP_PORT = process.env.APP_PORT || 9191; | ||
//TODO: refactor host usage | ||
export const APP_HOST = process.env.APP_HOST || "localhost"; | ||
export const SERVICE_PREFIX = process.env.SERVICE_PREFIX || 'sys_intercom'; | ||
export const AUTH_ENABLED = process.env.AUTH_ENABLED || false; | ||
export const AUTH_USER = process.env.AUTH_USER; | ||
export const AUTH_PASS = process.env.AUTH_PASS; | ||
|
||
// Intercom models | ||
export const BEWARD_DKS = 'BEWARD DKS' | ||
export const BEWARD_DS = 'BEWARD DS' | ||
export const QTECH = 'QTECH' | ||
export const AKUVOX = 'AKUVOX' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import 'dotenv/config' | ||
import express from 'express'; | ||
import { NODE_ENV, APP_HOST, APP_PORT } from './constants.js' | ||
import { showTitle } from "./utils/showTitle.js"; | ||
import routes from "./routes/routes.js"; | ||
|
||
const app = express(); | ||
app.use("/", routes) | ||
app.listen(APP_PORT, () => { | ||
NODE_ENV !== "production" && showTitle(); | ||
console.log(`Exporter server is running on http://${APP_HOST}:${APP_PORT}`); | ||
}); |
80 changes: 80 additions & 0 deletions
80
server/services/sys_exporter/app/metrics/devices/akuvox.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import DigestFetch from "digest-fetch"; | ||
|
||
/** | ||
* get Akuvox intercom metrics | ||
* @param url | ||
* @param username | ||
* @param password | ||
* @returns {Promise<{sipStatus: (number), uptimeSeconds: *}>} | ||
*/ | ||
export const getAkuvoxMetrics = async (url, username, password) => { | ||
console.log(`${new Date().toLocaleString("RU")} | getBewardMetrics: ${url}`); | ||
const digestClient = new DigestFetch(username, password); | ||
const BASE_URL = url + '/api'; | ||
const statusPayload = { | ||
target: 'system', | ||
action: 'status' | ||
}; | ||
const infoPayload = { | ||
target: 'system', | ||
action: 'info' | ||
}; | ||
|
||
class DigestClient { | ||
constructor(client, baseUrl) { | ||
this.client = client; | ||
this.baseUrl = baseUrl; | ||
} | ||
|
||
async post(endpoint, payload, timeout = 5000) { | ||
return new Promise((resolve, reject) => { | ||
const timer = setTimeout(() => { | ||
reject(new Error(`Request timeout: ${timeout} ms`)); | ||
}, timeout); | ||
|
||
this.client.fetch(this.baseUrl + endpoint, { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify(payload) | ||
}) | ||
.then(response => { | ||
clearTimeout(timer); | ||
if (!response.ok) { | ||
reject(new Error(`HTTP error! status: ${response.status}`)); | ||
} else { | ||
resolve(response.json()); | ||
} | ||
}) | ||
.catch(err => { | ||
clearTimeout(timer); | ||
reject(err); | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
const instance = new DigestClient(digestClient, BASE_URL); | ||
|
||
try { | ||
const [statusResponse, infoResponse] = await Promise.all([ | ||
instance.post('', statusPayload).then(({data}) => data), | ||
instance.post('', infoPayload).then(({data}) => data) | ||
]); | ||
|
||
const parseUptime = (data) => { | ||
return data.UpTime ?? 0; | ||
}; | ||
|
||
const parseSipStatus = (data) => { | ||
return data.Account1.Status === "2" ? 1 : 0; | ||
}; | ||
|
||
const sipStatus = parseSipStatus(infoResponse); | ||
const uptimeSeconds = parseUptime(statusResponse); | ||
|
||
return { sipStatus, uptimeSeconds }; | ||
} catch (err) { | ||
console.error(`${new Date().toLocaleString("RU")} | Error fetching metrics from device ${url}: ${err.message}`); | ||
throw new Error('Failed to fetch metrics from intercom'); | ||
} | ||
}; |
60 changes: 60 additions & 0 deletions
60
server/services/sys_exporter/app/metrics/devices/beward.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import axios from "axios"; | ||
|
||
export const getBewardMetrics = async (url, username = 'admin', password) => { | ||
console.log(`${new Date().toLocaleString("RU")} | getBewardMetrics: ${url}`); | ||
const BASE_URL = url + '/cgi-bin'; | ||
const PATH_SIP_STATUS = '/sip_cgi?action=regstatus&AccountReg'; | ||
const PATH_SYSINFO = '/systeminfo_cgi?action=get'; | ||
|
||
const instance = axios.create({ | ||
baseURL: BASE_URL, | ||
timeout: 1000, | ||
auth: { | ||
username: username, | ||
password: password | ||
} | ||
}); | ||
|
||
/** | ||
* Extract value of AccountReg1 | ||
* @param data | ||
* @returns {number|number} | ||
*/ | ||
const parseSipStatus = (data) => { | ||
const match = data.match(/AccountReg1=(\d+)/); | ||
return match ? parseInt(match[1], 10) : 0; | ||
}; | ||
|
||
/** | ||
* Extract value of UpTime and convert to seconds | ||
* @param data | ||
* @returns {number} | ||
* @example "UpTime=20:22:31", "UpTime=11.18:44:44" | ||
*/ | ||
const parseUptimeMatch = (data) => { | ||
const match = data.match(/UpTime=(?<days>\d+\.)?(?<hours>\d+\:)(?<minutes>\d+\:)(?<seconds>\d+)/); | ||
if (!match || !match.groups) { | ||
return 0; | ||
} | ||
const { days = 0, hours, minutes, seconds } = match.groups; | ||
return (parseInt(days, 10) * 24 * 3600) | ||
+ (parseInt(hours, 10) * 3600) | ||
+ (parseInt(minutes, 10) * 60) | ||
+ parseInt(seconds, 10); | ||
} | ||
|
||
try { | ||
const [sipStatusData, sysInfoData] = await Promise.all([ | ||
instance.get(PATH_SIP_STATUS).then(({data}) => data), | ||
instance.get(PATH_SYSINFO).then(({data}) => data) | ||
]); | ||
|
||
const sipStatus = parseSipStatus(sipStatusData); | ||
const uptimeSeconds = parseUptimeMatch(sysInfoData); | ||
|
||
return { sipStatus, uptimeSeconds }; | ||
} catch (err){ | ||
console.error(`${new Date().toLocaleString("RU")} | Error fetching metrics from device ${url}: ${err.message}`); | ||
throw new Error('Failed to fetch metrics from intercom'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import axios from "axios"; | ||
|
||
export const getQtechMetrics = async (url, username, password) => { | ||
console.log(`${new Date().toLocaleString("RU")} | getQtechMetrics: ${url}`); | ||
const BASE_URL = url + '/api'; | ||
const uptimePayload = { | ||
"target": "firmware", | ||
"action": "status", | ||
"data": {}, | ||
} | ||
const sipStatusPayload = { | ||
"target": "sip", | ||
"action": "get", | ||
"data": { | ||
"AccountId": 0 | ||
}, | ||
} | ||
const instance = axios.create({ | ||
baseURL: BASE_URL, | ||
timeout: 1000, | ||
auth: { | ||
username: username, | ||
password: password | ||
} | ||
}); | ||
const parseSipStatus = (data) => { | ||
return data?.SipAccount?.AccountStatus === "2" ? 1 : 0 | ||
} | ||
const parseUptime = (data) => { | ||
if (!data.UpTime){ | ||
return 0 | ||
} | ||
|
||
const upTime = data.UpTime | ||
// Регулярное выражение для поиска дней | ||
const dayMatch = upTime.match(/day:(\d+)/); | ||
|
||
// Регулярное выражение для поиска часов, минут и секунд, игнорируя лишние пробелы | ||
const timeMatch = upTime.match(/(\d+):\s*(\d+):\s*(\d+)/); | ||
|
||
if (dayMatch && timeMatch) { | ||
const days = parseInt(dayMatch[1], 10); | ||
const hours = parseInt(timeMatch[1].trim(), 10); | ||
const minutes = parseInt(timeMatch[2].trim(), 10); | ||
const seconds = parseInt(timeMatch[3].trim(), 10); | ||
|
||
return days * 86400 + hours * 3600 + minutes * 60 + seconds; | ||
} else { | ||
throw new Error(`Invalid UpTime format: ${upTime}`); | ||
} | ||
} | ||
|
||
try { | ||
const [ sysInfoData, sipStatusData, ] = await Promise.all([ | ||
instance.post('', JSON.stringify(uptimePayload)).then((res) => res.data.data), | ||
instance.post('', JSON.stringify(sipStatusPayload)).then((res) => res.data.data) | ||
]) | ||
|
||
const sipStatus = parseSipStatus(sipStatusData); | ||
const uptimeSeconds = parseUptime(sysInfoData); | ||
|
||
return { sipStatus, uptimeSeconds }; | ||
} catch (err){ | ||
console.error(`${new Date().toLocaleString("RU")} | Error fetching metrics from device ${url}: ${err.message}`); | ||
throw new Error('Failed to fetch metrics from intercom'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// device specific metrics | ||
export { getBewardMetrics } from './devices/beward.js' | ||
export { getQtechMetrics } from './devices/qtech.js' | ||
export { getAkuvoxMetrics } from './devices/akuvox.js' |
31 changes: 31 additions & 0 deletions
31
server/services/sys_exporter/app/metrics/metricsFactory.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { Gauge } from 'prom-client'; | ||
import { SERVICE_PREFIX } from '../constants.js' | ||
|
||
export const createMetrics = (registers, isGlobal = false) => { | ||
const sipStatusGauge = new Gauge({ | ||
name: `${SERVICE_PREFIX}_sip_status`, | ||
help: 'SIP status of the intercom. 0 = offline; 1 = online', | ||
labelNames: ['url'], | ||
registers: registers, | ||
}); | ||
|
||
const uptimeGauge = new Gauge({ | ||
name: `${SERVICE_PREFIX}_uptime_seconds`, | ||
help: 'Uptime of the intercom in seconds', | ||
labelNames: ['url'], | ||
registers: registers, | ||
}); | ||
|
||
const metrics = { sipStatusGauge, uptimeGauge }; | ||
|
||
if (!isGlobal) { | ||
metrics.probeSuccess = new Gauge({ | ||
name: 'probe_success', | ||
help: 'Displays whether or not the probe was a success', | ||
labelNames: ['url'], | ||
registers: registers, | ||
}); | ||
} | ||
|
||
return metrics; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Registry } from 'prom-client'; | ||
import { APP_NAME } from "../constants.js"; | ||
import { createMetrics } from "./metricsFactory.js"; | ||
|
||
// Create a global registry for all metrics | ||
export const globalRegistry = new Registry(); | ||
|
||
export const { | ||
sipStatusGauge: globalSipStatusGauge, | ||
uptimeGauge: globalUptimeGauge, | ||
} = createMetrics([globalRegistry], true); | ||
|
||
// Set default metrics | ||
globalRegistry.setDefaultLabels({ | ||
app: APP_NAME | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import basicAuth from "express-basic-auth"; | ||
import { AUTH_PASS, AUTH_USER } from "../constants.js"; | ||
|
||
const logUnauthorized = (req) => { | ||
console.log(`Failed auth: ${req.ip}`) | ||
} | ||
const basicAuthMiddleware = | ||
basicAuth({ | ||
users: {[AUTH_USER]: AUTH_PASS}, | ||
challenge: true, | ||
unauthorizedResponse: (req) => { | ||
logUnauthorized(req); | ||
return 'Failed auth'; | ||
} | ||
}) | ||
export default basicAuthMiddleware |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import express from "express"; | ||
import { globalRegistry } from "../metrics/registry.js"; | ||
|
||
const router = express.Router(); | ||
|
||
router.get("/", async (req, res) => { | ||
res.set('Content-Type', globalRegistry.contentType); | ||
res.end(await globalRegistry.metrics()); | ||
}) | ||
|
||
export default router; |
Oops, something went wrong.