Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ REDIS_TLS=false

BULL_QUEUE_NAME=notification

SAMA_URL=http://localhost:9001
SAMA_URL=http://localhost:9001 # If multiple nodes (discovering from redis) commed this env line
SAMA_ADMIN_API_KEY=**********

SERVER_UPDATE_INTERVAL=30000
Expand Down
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ const run = async () => {
serverAdapter.setBasePath('/ui');
app.register(serverAdapter.registerPlugin(), { prefix: '/ui' });

if (process.env.SAMA_URL) {
require('./sama-stats')(app);
if (process.env.SAMA_ADMIN_API_KEY) {
require('./sama-stats')(app, redisOptions);
}

await app.listen({ port: process.env.PORT, host: process.env.HOST });
Expand Down
64 changes: 53 additions & 11 deletions sama-stats/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
const path = require('node:path')
const Redis = require('ioredis')
const pointOfView = require('@fastify/view')

let redisConnection = void 0
let lastFetchDate = void 0
let lastServerStats = {}

const fetchSamaServerStats = async () => {
const fetchSamaServerStats = async (endpoint) => {
endpoint = endpoint.replace(/\/$/, '')

const serverStats = await fetch(
`${process.env.SAMA_URL}/admin/server-stats?format=1`,
`${endpoint}/admin/server-stats?format=1`,
{ method: 'GET', headers: {'Admin-Api-Key': process.env.SAMA_ADMIN_API_KEY} }
)
.then(response => response.json())
.then(serverStats => ({ serverStats: Object.assign(serverStats, { fetchDate: new Date().toString() }) }))
.catch(error => {
console.log('[Error][fetching]', error)
const message = `SAMA Server: ${error.message}`
Expand All @@ -19,13 +23,49 @@ const fetchSamaServerStats = async () => {
return serverStats
}

const startFetchingServerDate = () => {
setInterval(async () => {
lastServerStats = await fetchSamaServerStats()
}, process.env.SERVER_UPDATE_INTERVAL ?? 30_000)
const fetchClusterStats = async () => {
let clusterEndpoints = []

if (process.env.SAMA_URL) {
clusterEndpoints = process.env.SAMA_URL.split(',')
} else {
const listNodes = await redisConnection.keys('sama-node-data:*')

clusterEndpoints = listNodes
.map(key => key.replace('sama-node-data:', ''))
.map(wsEndpoint => {
const url = new URL(wsEndpoint)
url.protocol = 'http'
url.port = 9001
return url.toString()
})
}

console.log('[Endpoints]', clusterEndpoints)

const clusterStats = {}

for (const clusterEndpoint of clusterEndpoints) {
const stats = await fetchSamaServerStats(clusterEndpoint)

console.log('[Stats]', clusterEndpoint, stats)

clusterStats[clusterEndpoint] = stats
}

return clusterStats
}

const updateClusterStats = async () => {
lastServerStats = await fetchClusterStats()
lastFetchDate = new Date().toString()
}

module.exports = (fastifyApp) => {
const startFetchingServerStats = () => setInterval(updateClusterStats, process.env.SERVER_UPDATE_INTERVAL ?? 30_000)

module.exports = (fastifyApp, redisOptions) => {
redisConnection = new Redis(redisOptions)

fastifyApp.register(pointOfView, {
engine: {
ejs: require('ejs'),
Expand All @@ -37,17 +77,19 @@ module.exports = (fastifyApp) => {
method: 'GET',
url: '/stats/sama-server',
handler: (req, reply) => {
reply.view('sama-server-stats.ejs', { updateTime: process.env.CLIENT_UPDATE_INTERVAL ?? 10_000 });
reply.view('sama-server-stats.ejs', {
updateTime: process.env.CLIENT_UPDATE_INTERVAL ?? 10_000,
});
},
});

fastifyApp.route({
method: 'GET',
url: '/stats/data/sama-server',
handler: async (req, reply) => {
reply.send(lastServerStats)
reply.send({ fetchDate: lastFetchDate, stats: lastServerStats })
},
});

startFetchingServerDate()
startFetchingServerStats()
}
69 changes: 56 additions & 13 deletions sama-stats/views/sama-server-stats.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -13,54 +13,97 @@
</head>
<body>
<div>
<div>
<div style="margin-bottom: 20px;">
<div>Server Fetch date: <span id="fetch-date"></span></div>
<div>Client Update date: <span id="update-date"></span></div>
<div>Nodes List: <span id="nodes-list"></span></div>
</div>
<div id="ServerStats"></div>
</div>

<script>
const elemId = 'ServerStats'
const container = document.getElementById(elemId);
const editor = new JSONEditor(container, { mode: "view" });
const viewsContainerId = 'ServerStats'
const editors = {}

const fetchDateId = 'fetch-date'
const updateDateId = 'update-date'
const nodesListId = 'nodes-list'

async function fetchServerStats() {
const url = `${location.origin}/stats/data/sama-server`
const stats = await fetch(url, { method: "GET" })
const statsResponse = await fetch(url, { method: "GET" })
.then(response => response.json())
.catch(error => {
console.log('[Error][fetching]', url, error)
return { error: error?.message }
})

return stats
return statsResponse
}

async function fetchAndDisplayServerStats() {
const updateDate = new Date().toString()
let fetchDate = void 0

const stats = await fetchServerStats()
const statsResponse = await fetchServerStats()

if (stats?.serverStats?.fetchDate) {
fetchDate = stats.serverStats.fetchDate
delete stats.serverStats.fetchDate
console.log('[serverStats]', statsResponse)

displayStats(statsResponse)
}

function displayStats(statsResponse) {
const updateDate = new Date().toString()

const fetchDate = statsResponse?.fetchDate
delete statsResponse.fetchDate
let nodesStats = statsResponse?.stats

if (!nodesStats) {
nodesStats = {}
for (const endpoint of Object.keys(editors)) {
nodesStats[endpoint] = statsResponse
}
}

const nodeEndpoints = Object.keys(nodesStats)
for (const nodeEndpoint of nodeEndpoints) {
displayNodeStatsInView(nodeEndpoint, nodesStats[nodeEndpoint])
}

console.log('[serverStats]', stats, fetchDate, updateDate)
document.getElementById(nodesListId).textContent = JSON.stringify(nodeEndpoints)

const dataView = stats?.serverStats ?? stats
// remove old
for (const oldEndpoint of Object.keys(editors)) {
const exist = nodeEndpoints.some(endpoint => endpoint === oldEndpoint)

editor.set(dataView);
if (!exist) {
document.getElementById(`view_${oldEndpoint}`)?.remove()
delete editors[oldEndpoint]
}
}

document.getElementById(updateDateId).textContent = updateDate
document.getElementById(fetchDateId).textContent = fetchDate ?? '-'
}

function displayNodeStatsInView(endpoint, stats) {
const statsTargetId = `view_${endpoint}`
let existedContainer = document.getElementById(statsTargetId)
let editor = editors[endpoint]

if (!existedContainer) {
existedContainer = document.createElement('div')
existedContainer.id = statsTargetId
existedContainer.className = 'json_view'
existedContainer.textContent = endpoint
document.getElementById(viewsContainerId).appendChild(existedContainer)
editors[endpoint] = editor = new JSONEditor(existedContainer, { mode: "view" })
}

editor.set(stats)
}

setInterval(fetchAndDisplayServerStats, +<%= updateTime %>)
fetchAndDisplayServerStats()
</script>
Expand Down