Skip to content

Commit

Permalink
Add /metrics endpoint for prometheus support (#512)
Browse files Browse the repository at this point in the history
  • Loading branch information
Leonid Tyurin authored Feb 26, 2021
1 parent 9fd3f6a commit f64f8b1
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 4 deletions.
14 changes: 12 additions & 2 deletions monitor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require('dotenv').config()
const express = require('express')
const cors = require('cors')
const { readFile } = require('./utils/file')
const { getPrometheusMetrics } = require('./prometheusMetrics')

const app = express()
const bridgeRouter = express.Router({ mergeParams: true })
Expand All @@ -11,17 +12,26 @@ app.use(cors())
app.get('/favicon.ico', (req, res) => res.sendStatus(204))
app.use('/:bridgeName', bridgeRouter)

bridgeRouter.get('/:file(validators|eventsStats|alerts|mediators|stuckTransfers|failures)?', async (req, res, next) => {
bridgeRouter.get('/:file(validators|eventsStats|alerts|mediators|stuckTransfers|failures)?', (req, res, next) => {
try {
const { bridgeName, file } = req.params
const results = await readFile(`./responses/${bridgeName}/${file || 'getBalances'}.json`)
const results = readFile(`./responses/${bridgeName}/${file || 'getBalances'}.json`)
res.json(results)
} catch (e) {
// this will eventually be handled by your error handling middleware
next(e)
}
})

bridgeRouter.get('/metrics', (req, res, next) => {
try {
const metrics = getPrometheusMetrics(req.params.bridgeName)
res.type('text').send(metrics)
} catch (e) {
next(e)
}
})

const port = process.env.MONITOR_PORT || 3003
app.set('port', port)
app.listen(port, () => console.log(`Monitoring app listening on port ${port}!`))
104 changes: 104 additions & 0 deletions monitor/prometheusMetrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
const { readFile } = require('./utils/file')

const {
MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST,
MONITOR_HOME_TO_FOREIGN_BLOCK_LIST,
MONITOR_HOME_VALIDATORS_BALANCE_ENABLE,
MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE
} = process.env

function BridgeConf(type, validatorsBalanceEnable, alertTargetFunc, failureDirection) {
this.type = type
this.validatorsBalanceEnable = validatorsBalanceEnable
this.alertTargetFunc = alertTargetFunc
this.failureDirection = failureDirection
}

const BRIDGE_CONFS = [
new BridgeConf('home', MONITOR_HOME_VALIDATORS_BALANCE_ENABLE, 'executeAffirmations', 'homeToForeign'),
new BridgeConf('foreign', MONITOR_FOREIGN_VALIDATORS_BALANCE_ENABLE, 'executeSignatures', 'foreignToHome')
]

function hasError(obj) {
return 'error' in obj
}

function getPrometheusMetrics(bridgeName) {
const responsePath = jsonName => `./responses/${bridgeName}/${jsonName}.json`

const metrics = {}

// Balance metrics
const balancesFile = readFile(responsePath('getBalances'))

if (!hasError(balancesFile)) {
const { home: homeBalances, foreign: foreignBalances, ...commonBalances } = balancesFile
metrics.balances_home_value = homeBalances.totalSupply
metrics.balances_home_txs_deposit = homeBalances.deposits
metrics.balances_home_txs_withdrawal = homeBalances.withdrawals

metrics.balances_foreign_value = foreignBalances.erc20Balance
metrics.balances_foreign_txs_deposit = foreignBalances.deposits
metrics.balances_foreign_txs_withdrawal = foreignBalances.withdrawals

metrics.balances_diff_value = commonBalances.balanceDiff
metrics.balances_diff_deposit = commonBalances.depositsDiff
metrics.balances_diff_withdrawal = commonBalances.withdrawalDiff
if (MONITOR_HOME_TO_FOREIGN_ALLOWANCE_LIST || MONITOR_HOME_TO_FOREIGN_BLOCK_LIST) {
metrics.balances_unclaimed_txs = commonBalances.unclaimedDiff
metrics.balances_unclaimed_value = commonBalances.unclaimedBalance
}
}

// Validator metrics
const validatorsFile = readFile(responsePath('validators'))

if (!hasError(validatorsFile)) {
for (const bridge of BRIDGE_CONFS) {
const allValidators = validatorsFile[bridge.type].validators
const validatorAddressesWithBalanceCheck =
typeof bridge.validatorsBalanceEnable === 'string'
? bridge.validatorsBalanceEnable.split(' ')
: Object.keys(allValidators)

validatorAddressesWithBalanceCheck.forEach((addr, ind) => {
metrics[`validators_balances_${bridge.type}${ind}{address="${addr}"}`] = allValidators[addr].balance
})
}
}

// Alert metrics
const alertsFile = readFile(responsePath('alerts'))

if (!hasError(alertsFile)) {
for (const bridge of BRIDGE_CONFS) {
Object.entries(alertsFile[bridge.alertTargetFunc].misbehavior).forEach(([period, val]) => {
metrics[`misbehavior_${bridge.type}_${period}`] = val
})
}
}

// Failure metrics
const failureFile = readFile(responsePath('failures'))

if (!hasError(failureFile)) {
for (const bridge of BRIDGE_CONFS) {
const dir = bridge.failureDirection
const failures = failureFile[dir]
metrics[`failures_${dir}_total`] = failures.total
Object.entries(failures.stats).forEach(([period, count]) => {
metrics[`failures_${dir}_${period}`] = count
})
}
}

// Pack metrcis into a plain text
return Object.entries(metrics).reduceRight(
// Prometheus supports `Nan` and possibly signed `Infinity`
// in case cast to `Number` fails
(acc, [key, val]) => `${key} ${val ? Number(val) : 0}\n${acc}`,
''
)
}

module.exports = { getPrometheusMetrics }
4 changes: 2 additions & 2 deletions monitor/utils/file.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const fs = require('fs')
const path = require('path')

async function readFile(filePath) {
function readFile(filePath) {
try {
const content = await fs.readFileSync(filePath)
const content = fs.readFileSync(filePath)
const json = JSON.parse(content)
const timeDiff = Math.floor(Date.now() / 1000) - json.lastChecked
return Object.assign({}, json, { timeDiff })
Expand Down

0 comments on commit f64f8b1

Please sign in to comment.