diff --git a/lib/machine-loader.js b/lib/machine-loader.js index b449e68b..d5f41415 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -1,5 +1,7 @@ const _ = require('lodash/fp') +const pgp = require('pg-promise')() const axios = require('axios') +const uuid = require('uuid') const db = require('./db') const pairing = require('./pairing') @@ -39,11 +41,14 @@ function getMachineNames (config) { const unresponsiveStatus = { label: 'Unresponsive', type: 'error' } const stuckStatus = { label: 'Stuck', type: 'error' } - return Promise.all([getMachines(), getConfig(config)]) - .then(([machines, config]) => Promise.all( - [machines, checkPings(machines), dbm.machineEvents(), config] + return Promise.all([getMachines(), getConfig(config), getNetworkHeartbeat(), getNetworkPerformance()]) + .then(([rawMachines, config, heartbeat, performance]) => Promise.all( + [rawMachines, checkPings(rawMachines), dbm.machineEvents(), config, heartbeat, performance] )) - .then(([machines, pings, events, config]) => { + .then(([rawMachines, pings, events, config, heartbeat, performance]) => { + const mergeByDeviceId = (x, y) => _.values(_.merge(_.keyBy('deviceId', x), _.keyBy('deviceId', y))) + const machines = mergeByDeviceId(mergeByDeviceId(rawMachines, heartbeat), performance) + const getStatus = (ping, stuck) => { if (ping && ping.age) return unresponsiveStatus @@ -66,7 +71,6 @@ function getMachineNames (config) { return _.assign(r, { cashOut, statuses }) } - return _.map(addName, machines) }) } @@ -143,4 +147,56 @@ function setMachine (rec) { } } -module.exports = { getMachineName, getMachines, getMachine, getMachineNames, setMachine } +function updateNetworkPerformance (deviceId, data) { + const downloadSpeed = _.head(data) + const dbData = { + device_id: deviceId, + download_speed: downloadSpeed.speed, + created: new Date() + } + const cs = new pgp.helpers.ColumnSet(['device_id', 'download_speed', 'created'], + { table: 'machine_network_performance' }) + const onConflict = ' ON CONFLICT (device_id) DO UPDATE SET ' + + cs.assignColumns({ from: 'EXCLUDED', skip: ['device_id'] }) + const upsert = pgp.helpers.insert(dbData, cs) + onConflict + return db.none(upsert) +} + +function updateNetworkHeartbeat (deviceId, data) { + const avgResponseTime = _.meanBy(e => _.toNumber(e.averageResponseTime), data) + const avgPacketLoss = _.meanBy(e => _.toNumber(e.packetLoss), data) + const dbData = { + id: uuid.v4(), + device_id: deviceId, + average_response_time: avgResponseTime, + average_packet_loss: avgPacketLoss + } + const sql = pgp.helpers.insert(dbData, null, 'machine_network_heartbeat') + return db.none(sql) +} + +function getNetworkPerformance () { + const sql = `SELECT device_id, download_speed FROM machine_network_performance` + return db.manyOrNone(sql) + .then(res => _.map(_.mapKeys(_.camelCase))(res)) +} + +function getNetworkHeartbeat () { + const sql = `SELECT AVG(average_response_time) AS response_time, AVG(average_packet_loss) AS packet_loss, device_id + FROM machine_network_heartbeat + GROUP BY device_id` + return db.manyOrNone(sql) + .then(res => _.map(_.mapKeys(_.camelCase))(res)) +} + +module.exports = { + getMachineName, + getMachines, + getMachine, + getMachineNames, + setMachine, + updateNetworkPerformance, + updateNetworkHeartbeat, + getNetworkPerformance, + getNetworkHeartbeat +} diff --git a/lib/new-admin/graphql/types/machine.type.js b/lib/new-admin/graphql/types/machine.type.js index 5fb80dab..b50f28b7 100644 --- a/lib/new-admin/graphql/types/machine.type.js +++ b/lib/new-admin/graphql/types/machine.type.js @@ -19,6 +19,9 @@ const typeDef = gql` cassette2: Int statuses: [MachineStatus] latestEvent: MachineEvent + downloadSpeed: String + responseTime: String + packetLoss: String } type MachineEvent { diff --git a/lib/plugins.js b/lib/plugins.js index 985f7365..82bfc3bc 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -289,6 +289,23 @@ function plugins (settings, deviceId) { ]) } + function pruneMachinesHeartbeat () { + const sql = `DELETE + FROM + machine_network_heartbeat h + USING (SELECT + device_id, + max(created) as lastEntry + FROM + machine_network_heartbeat + GROUP BY + device_id) d + WHERE + d.device_id = h.device_id + AND h.created < d.lastEntry` + db.none(sql) + } + function isHd (tx) { return wallet.isHd(settings, tx) } @@ -795,7 +812,8 @@ function plugins (settings, deviceId) { sell, getNotificationConfig, notifyOperator, - fetchCurrentConfigVersion + fetchCurrentConfigVersion, + pruneMachinesHeartbeat } } diff --git a/lib/poller.js b/lib/poller.js index e03bd689..52d43f76 100644 --- a/lib/poller.js +++ b/lib/poller.js @@ -24,6 +24,7 @@ const LOGS_CLEAR_INTERVAL = 1 * T.day const SANCTIONS_INITIAL_DOWNLOAD_INTERVAL = 5 * T.minutes const SANCTIONS_UPDATE_INTERVAL = 1 * T.week const RADAR_UPDATE_INTERVAL = 5 * T.minutes +const PRUNE_MACHINES_HEARBEAT = 1 * T.day const CHECK_NOTIFICATION_INTERVAL = 20 * T.seconds @@ -102,6 +103,7 @@ function start (__settings) { setInterval(initialSanctionsDownload, SANCTIONS_INITIAL_DOWNLOAD_INTERVAL) setInterval(updateAndLoadSanctions, SANCTIONS_UPDATE_INTERVAL) setInterval(updateCoinAtmRadar, RADAR_UPDATE_INTERVAL) + setInterval(() => pi().pruneMachinesHeartbeat(), PRUNE_MACHINES_HEARBEAT) } module.exports = { start, reload } diff --git a/lib/routes/performanceRoutes.js b/lib/routes/performanceRoutes.js index db34bd62..d09330c2 100644 --- a/lib/routes/performanceRoutes.js +++ b/lib/routes/performanceRoutes.js @@ -1,25 +1,17 @@ const express = require('express') const router = express.Router() -const { getMachine } = require('../machine-loader') +const { updateNetworkHeartbeat, updateNetworkPerformance } = require('../machine-loader') function networkHeartbeat (req, res, next) { - return getMachine(req.deviceId) - .then(machine => { - console.log(`${machine.name} network heartbeat:`) - console.log(req.body) - return res.status(200).send({ status: 'OK' }) - }) + return updateNetworkHeartbeat(req.deviceId, req.body) + .then(() => res.status(200).send({ status: 'OK' })) .catch(next) } function networkPerformance (req, res, next) { - return getMachine(req.deviceId) - .then(machine => { - console.log(`${machine.name} network performance:`) - console.log(req.body) - return res.status(200).send({ status: 'OK' }) - }) + return updateNetworkPerformance(req.deviceId, req.body) + .then(() => res.status(200).send({ status: 'OK' })) .catch(next) } diff --git a/migrations/1626275844773-add-machine-network-performance.js b/migrations/1626275844773-add-machine-network-performance.js new file mode 100644 index 00000000..344714ac --- /dev/null +++ b/migrations/1626275844773-add-machine-network-performance.js @@ -0,0 +1,26 @@ +const db = require('./db') + +exports.up = function (next) { + var sql = [ + 'drop table if exists machine_network_heartbeat', + 'drop table if exists machine_network_performance', + `create table machine_network_performance ( + device_id text PRIMARY KEY, + download_speed numeric NOT NULL, + created timestamptz NOT NULL default now() + )`, + `create table machine_network_heartbeat ( + id uuid PRIMARY KEY, + device_id text not null, + average_response_time numeric NOT NULL, + average_packet_loss numeric NOT NULL, + created timestamptz NOT NULL default now() + )` + ] + + db.multi(sql, next) +} + +exports.down = function (next) { + next() +} diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js index 11f9f295..66f2076e 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Overview.js @@ -1,5 +1,6 @@ import { useMutation } from '@apollo/react-hooks' import { makeStyles } from '@material-ui/core/styles' +import BigNumber from 'bignumber.js' import gql from 'graphql-tag' import moment from 'moment' import React, { useState } from 'react' @@ -88,6 +89,37 @@ const Overview = ({ data, onActionSuccess }) => {
{makeLastPing(data.lastPing)}
++ {data.downloadSpeed + ? new BigNumber(data.downloadSpeed).toFixed(4).toString() + + ' MB/s' + : 'unavailable'} +
++ {data.responseTime + ? new BigNumber(data.responseTime).toFixed(3).toString() + ' ms' + : 'unavailable'} +
++ {data.packetLoss + ? new BigNumber(data.packetLoss).toFixed(3).toString() + ' %' + : 'unavailable'} +
+