feat: ping time internet quality measurement
This commit is contained in:
parent
f2f146265e
commit
515d02dd21
7 changed files with 162 additions and 17 deletions
|
|
@ -1,5 +1,7 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
|
const pgp = require('pg-promise')()
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
|
const uuid = require('uuid')
|
||||||
|
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const pairing = require('./pairing')
|
const pairing = require('./pairing')
|
||||||
|
|
@ -56,19 +58,21 @@ function addName (pings, events, config) {
|
||||||
getStatus(
|
getStatus(
|
||||||
_.first(pings[machine.deviceId]),
|
_.first(pings[machine.deviceId]),
|
||||||
_.first(checkStuckScreen(events, machine.name))
|
_.first(checkStuckScreen(events, machine.name))
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
return _.assign(machine, { cashOut, statuses })
|
return _.assign(machine, { cashOut, statuses })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMachineNames (config) {
|
function getMachineNames (config) {
|
||||||
return Promise.all([getMachines(), getConfig(config)])
|
return Promise.all([getMachines(), getConfig(config), getNetworkHeartbeat(), getNetworkPerformance()])
|
||||||
.then(([machines, config]) => Promise.all(
|
.then(([rawMachines, config, heartbeat, performance]) => Promise.all(
|
||||||
[machines, checkPings(machines), dbm.machineEvents(), config]
|
[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)
|
||||||
return machines.map(addName(pings, events, config))
|
return machines.map(addName(pings, events, config))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -105,8 +109,8 @@ function getMachine (machineId, config) {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return Promise.all([queryMachine, dbm.machineEvents(), config])
|
return Promise.all([queryMachine, dbm.machineEvents(), config])
|
||||||
.then(([machine, events, config]) => {
|
.then(([machine, events, config]) => {
|
||||||
const pings = checkPings([machine])
|
const pings = checkPings([machine])
|
||||||
|
|
||||||
return [machine].map(addName(pings, events, config))[0]
|
return [machine].map(addName(pings, events, config))[0]
|
||||||
})
|
})
|
||||||
|
|
@ -163,4 +167,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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,9 @@ const typeDefs = gql`
|
||||||
cassette2: Int
|
cassette2: Int
|
||||||
statuses: [MachineStatus]
|
statuses: [MachineStatus]
|
||||||
latestEvent: MachineEvent
|
latestEvent: MachineEvent
|
||||||
|
downloadSpeed: String
|
||||||
|
responseTime: String
|
||||||
|
packetLoss: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Customer {
|
type Customer {
|
||||||
|
|
|
||||||
|
|
@ -276,6 +276,13 @@ 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) {
|
function isHd (tx) {
|
||||||
return wallet.isHd(settings, tx.cryptoCode)
|
return wallet.isHd(settings, tx.cryptoCode)
|
||||||
}
|
}
|
||||||
|
|
@ -743,7 +750,8 @@ function plugins (settings, deviceId) {
|
||||||
sell,
|
sell,
|
||||||
getNotificationConfig,
|
getNotificationConfig,
|
||||||
notifyOperator,
|
notifyOperator,
|
||||||
fetchCurrentConfigVersion
|
fetchCurrentConfigVersion,
|
||||||
|
pruneMachinesHeartbeat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ const LOGS_CLEAR_INTERVAL = 1 * T.day
|
||||||
const SANCTIONS_INITIAL_DOWNLOAD_INTERVAL = 5 * T.minutes
|
const SANCTIONS_INITIAL_DOWNLOAD_INTERVAL = 5 * T.minutes
|
||||||
const SANCTIONS_UPDATE_INTERVAL = 1 * T.week
|
const SANCTIONS_UPDATE_INTERVAL = 1 * T.week
|
||||||
const RADAR_UPDATE_INTERVAL = 5 * T.minutes
|
const RADAR_UPDATE_INTERVAL = 5 * T.minutes
|
||||||
|
const PRUNE_MACHINES_HEARBEAT = 1 * T.day
|
||||||
|
|
||||||
const CHECK_NOTIFICATION_INTERVAL = 20 * T.seconds
|
const CHECK_NOTIFICATION_INTERVAL = 20 * T.seconds
|
||||||
|
|
||||||
|
|
@ -102,6 +103,7 @@ function start (__settings) {
|
||||||
setInterval(initialSanctionsDownload, SANCTIONS_INITIAL_DOWNLOAD_INTERVAL)
|
setInterval(initialSanctionsDownload, SANCTIONS_INITIAL_DOWNLOAD_INTERVAL)
|
||||||
setInterval(updateAndLoadSanctions, SANCTIONS_UPDATE_INTERVAL)
|
setInterval(updateAndLoadSanctions, SANCTIONS_UPDATE_INTERVAL)
|
||||||
setInterval(updateCoinAtmRadar, RADAR_UPDATE_INTERVAL)
|
setInterval(updateCoinAtmRadar, RADAR_UPDATE_INTERVAL)
|
||||||
|
setInterval(() => pi().pruneMachinesHeartbeat(), PRUNE_MACHINES_HEARBEAT)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { start, reload }
|
module.exports = { start, reload }
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const dbErrorCodes = require('./db-error-codes')
|
||||||
const options = require('./options')
|
const options = require('./options')
|
||||||
const logger = require('./logger')
|
const logger = require('./logger')
|
||||||
const configManager = require('./new-config-manager')
|
const configManager = require('./new-config-manager')
|
||||||
|
const machineLoader = require('./machine-loader')
|
||||||
const complianceTriggers = require('./compliance-triggers')
|
const complianceTriggers = require('./compliance-triggers')
|
||||||
const pairing = require('./pairing')
|
const pairing = require('./pairing')
|
||||||
const newSettingsLoader = require('./new-settings-loader')
|
const newSettingsLoader = require('./new-settings-loader')
|
||||||
|
|
@ -243,6 +244,18 @@ function verifyPromoCode (req, res, next) {
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function networkHeartbeat (req, res, next) {
|
||||||
|
return machineLoader.updateNetworkHeartbeat(req.deviceId, req.body)
|
||||||
|
.then(() => res.status(200).send({ status: 'OK' }))
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkPerformance (req, res, next) {
|
||||||
|
return machineLoader.updateNetworkPerformance(req.deviceId, req.body)
|
||||||
|
.then(() => res.status(200).send({ status: 'OK' }))
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
function addOrUpdateCustomer (req) {
|
function addOrUpdateCustomer (req) {
|
||||||
const customerData = req.body
|
const customerData = req.body
|
||||||
const machineVersion = req.query.version
|
const machineVersion = req.query.version
|
||||||
|
|
@ -516,6 +529,9 @@ app.get('/poll', poll)
|
||||||
app.get('/terms_conditions', getTermsConditions)
|
app.get('/terms_conditions', getTermsConditions)
|
||||||
app.post('/state', stateChange)
|
app.post('/state', stateChange)
|
||||||
|
|
||||||
|
app.post('/network/heartbeat', networkHeartbeat)
|
||||||
|
app.post('/network/performance', networkPerformance)
|
||||||
|
|
||||||
app.post('/verify_user', verifyUser)
|
app.post('/verify_user', verifyUser)
|
||||||
app.post('/verify_transaction', verifyTx)
|
app.post('/verify_transaction', verifyTx)
|
||||||
app.post('/verify_promo_code', verifyPromoCode)
|
app.post('/verify_promo_code', verifyPromoCode)
|
||||||
|
|
|
||||||
26
migrations/1626275844773-add-machine-network-performance.js
Normal file
26
migrations/1626275844773-add-machine-network-performance.js
Normal file
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useQuery } from '@apollo/react-hooks'
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
|
|
@ -33,6 +34,9 @@ const GET_MACHINES = gql`
|
||||||
label
|
label
|
||||||
type
|
type
|
||||||
}
|
}
|
||||||
|
downloadSpeed
|
||||||
|
responseTime
|
||||||
|
packetLoss
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -48,25 +52,55 @@ const MachineStatus = () => {
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
header: 'Machine Name',
|
header: 'Machine Name',
|
||||||
width: 250,
|
width: 150,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
view: m => m.name
|
view: m => m.name
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Status',
|
header: 'Status',
|
||||||
width: 350,
|
width: 150,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
view: m => <MainStatus statuses={m.statuses} />
|
view: m => <MainStatus statuses={m.statuses} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Last ping',
|
header: 'Last ping',
|
||||||
width: 200,
|
width: 175,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
view: m => (m.lastPing ? moment(m.lastPing).fromNow() : 'unknown')
|
view: m => (m.lastPing ? moment(m.lastPing).fromNow() : 'unknown')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: 'Network speed',
|
||||||
|
width: 150,
|
||||||
|
size: 'sm',
|
||||||
|
textAlign: 'left',
|
||||||
|
view: m =>
|
||||||
|
m.downloadSpeed
|
||||||
|
? new BigNumber(m.downloadSpeed).toFixed(4).toString() + ' MB/s'
|
||||||
|
: 'unavailable'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Latency',
|
||||||
|
width: 150,
|
||||||
|
size: 'sm',
|
||||||
|
textAlign: 'left',
|
||||||
|
view: m =>
|
||||||
|
m.responseTime
|
||||||
|
? new BigNumber(m.responseTime).toFixed(3).toString() + ' ms'
|
||||||
|
: 'unavailable'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Packet Loss',
|
||||||
|
width: 125,
|
||||||
|
size: 'sm',
|
||||||
|
textAlign: 'left',
|
||||||
|
view: m =>
|
||||||
|
m.packetLoss
|
||||||
|
? new BigNumber(m.packetLoss).toFixed(3).toString() + ' %'
|
||||||
|
: 'unavailable'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: 'Software Version',
|
header: 'Software Version',
|
||||||
width: 200,
|
width: 200,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue