diff --git a/packages/backend/src/aggregator.ts b/packages/backend/src/aggregator.ts index 7261d88..1995156 100644 --- a/packages/backend/src/aggregator.ts +++ b/packages/backend/src/aggregator.ts @@ -20,13 +20,14 @@ export default class Aggregator extends EventEmitter { this.broadcast(Feed.addedNode(node)); node.once('disconnect', () => { - node.removeAllListeners('block'); + node.removeAllListeners(); this.nodes.remove(node); this.broadcast(Feed.removedNode(node)); }); node.on('block', () => this.updateBlock(node)); + node.on('stats', () => this.broadcast(Feed.stats(node))); } public addFeed(feed: Feed) { @@ -40,7 +41,7 @@ export default class Aggregator extends EventEmitter { feed.once('disconnect', () => { this.feeds.remove(feed); - }) + }); } public nodeList(): IterableIterator { diff --git a/packages/backend/src/feed.ts b/packages/backend/src/feed.ts index d44a9a5..b5b02e9 100644 --- a/packages/backend/src/feed.ts +++ b/packages/backend/src/feed.ts @@ -41,7 +41,7 @@ export default class Feed extends EventEmitter { public static addedNode(node: Node): FeedData { return serialize({ action: 'added', - payload: [node.id, node.nodeDetails(), node.blockDetails()] + payload: [node.id, node.nodeDetails(), node.nodeStats(), node.blockDetails()] }) } @@ -59,6 +59,13 @@ export default class Feed extends EventEmitter { }); } + public static stats(node: Node): FeedData { + return serialize({ + action: 'stats', + payload: [node.id, node.nodeStats()] + }); + } + public send(data: FeedData) { this.socket.send(data); } diff --git a/packages/backend/src/message.ts b/packages/backend/src/message.ts index 81f0a47..cff5500 100644 --- a/packages/backend/src/message.ts +++ b/packages/backend/src/message.ts @@ -34,7 +34,7 @@ interface MessageBase { } export interface BestBlock { - best: string, + best: Types.BlockHash, height: Types.BlockNumber, ts: Date, } @@ -44,14 +44,14 @@ interface SystemConnected { name: Types.NodeName, chain: string, config: string, - implementation: string, - version: string, + implementation: Types.NodeImplementation, + version: Types.NodeVersion, } -interface SystemInterval extends BestBlock { +export interface SystemInterval extends BestBlock { msg: 'system.interval', - txcount: number, - peers: number, + txcount: Types.TransactionCount, + peers: Types.PeerCount, status: 'Idle' | string, // TODO: 'Idle' | ...? } diff --git a/packages/backend/src/node.ts b/packages/backend/src/node.ts index 5e04913..05a7d0b 100644 --- a/packages/backend/src/node.ts +++ b/packages/backend/src/node.ts @@ -1,10 +1,10 @@ import * as WebSocket from 'ws'; import * as EventEmitter from 'events'; import { Maybe, Types, idGenerator } from '@dotstats/common'; -import { parseMessage, getBestBlock, Message, BestBlock } from './message'; +import { parseMessage, getBestBlock, Message, BestBlock, SystemInterval } from './message'; const BLOCK_TIME_HISTORY = 10; -const TIMEOUT = 1000 * 60 * 5; // 5 seconds +const TIMEOUT = 1000 * 60 * 1; // 1 minute const nextId = idGenerator(); @@ -12,18 +12,27 @@ export default class Node extends EventEmitter { public lastMessage: number; public id: Types.NodeId; public name: Types.NodeName; - public implementation: string; - public version: string; + public implementation: Types.NodeImplementation; + public version: Types.NodeVersion; public config: string; public height = 0 as Types.BlockNumber; public latency = 0 as Types.Milliseconds; public blockTime = 0 as Types.Milliseconds; + private peers = 0 as Types.PeerCount; + private txcount = 0 as Types.TransactionCount; + private socket: WebSocket; private blockTimes: Array = new Array(BLOCK_TIME_HISTORY); private lastBlockAt: Maybe = null; - constructor(socket: WebSocket, name: Types.NodeName, config: string, implentation: string, version: string) { + constructor( + socket: WebSocket, + name: Types.NodeName, + config: string, + implentation: Types.NodeImplementation, + version: Types.NodeVersion, + ) { super(); this.lastMessage = Date.now(); @@ -37,11 +46,11 @@ export default class Node extends EventEmitter { console.log(`Listening to a new node: ${name}`); socket.on('message', (data) => { - console.log(data); - const message = parseMessage(data); - if (!message) return; + if (!message) { + return; + } this.lastMessage = Date.now(); this.updateLatency(message.ts); @@ -51,6 +60,10 @@ export default class Node extends EventEmitter { if (update) { this.updateBestBlock(update); } + + if (message.msg === 'system.interval') { + this.onSystemInterval(message); + } }); socket.on('close', () => { @@ -74,8 +87,6 @@ export default class Node extends EventEmitter { } function handler(data: WebSocket.Data) { - console.log(data); - const message = parseMessage(data); if (message && message.msg === "system.connected") { @@ -106,16 +117,15 @@ export default class Node extends EventEmitter { } public nodeDetails(): Types.NodeDetails { - return { - name: this.name, - }; + return [this.name, this.implementation, this.version]; + } + + public nodeStats(): Types.NodeStats { + return [this.peers, this.txcount]; } public blockDetails(): Types.BlockDetails { - return { - height: this.height, - blockTime: this.blockTime, - }; + return [this.height, this.blockTime]; } public get average(): number { @@ -143,6 +153,17 @@ export default class Node extends EventEmitter { this.emit('disconnect'); } + private onSystemInterval(message: SystemInterval) { + const { peers, txcount } = message; + + if (this.peers !== peers || this.txcount !== txcount) { + this.peers = peers; + this.txcount = txcount; + + this.emit('stats'); + } + } + private updateLatency(time: Date) { this.latency = (this.lastMessage - +time) as Types.Milliseconds; } diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index ece6e20..5ed1008 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -4,6 +4,6 @@ "outDir": "build" }, "include": [ - "src/**/*.ts" + "./src/**/*.ts" ] } diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 667830f..167ee5b 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -4,17 +4,17 @@ import { Id } from './id'; export type FeedId = Id<'Feed'>; export type NodeId = Id<'Node'>; export type NodeName = Opaque; +export type NodeImplementation = Opaque; +export type NodeVersion = Opaque; export type BlockNumber = Opaque; +export type BlockHash = Opaque; export type Milliseconds = Opaque; +export type PeerCount = Opaque; +export type TransactionCount = Opaque; -export interface BlockDetails { - height: BlockNumber; - blockTime: Milliseconds; -} - -export interface NodeDetails { - name: NodeName; -} +export type BlockDetails = [BlockNumber, Milliseconds]; +export type NodeDetails = [NodeName, NodeImplementation, NodeVersion]; +export type NodeStats = [PeerCount, TransactionCount]; interface BestBlock { action: 'best'; @@ -23,7 +23,7 @@ interface BestBlock { interface AddedNode { action: 'added'; - payload: [NodeId, NodeDetails, BlockDetails]; + payload: [NodeId, NodeDetails, NodeStats, BlockDetails]; } interface RemovedNode { @@ -36,4 +36,9 @@ interface Imported { payload: [NodeId, BlockDetails]; } -export type FeedMessage = BestBlock | AddedNode | RemovedNode | Imported; +interface Stats { + action: 'stats'; + payload: [NodeId, NodeStats]; +} + +export type FeedMessage = BestBlock | AddedNode | RemovedNode | Imported | Stats; diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index a9d9bfa..9fc8259 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -4,6 +4,7 @@ import { Types } from '@dotstats/common'; interface Node { nodeDetails: Types.NodeDetails, + nodeStats: Types.NodeStats, blockDetails: Types.BlockDetails, } @@ -35,18 +36,27 @@ export default class App extends React.Component<{}, State> { - + { - this.nodes().map(([ id, node ]) => ( - - - - - - )) + this.nodes().map(([ id, node ]) => { + const [name, implementation, version] = node.nodeDetails; + const [height, blockTime] = node.blockDetails; + const [peers, txcount] = node.nodeStats; + + return ( + + + + + + + + + ); + }) }
NameBlockBlock timeNode NameNode TypePeersTransactionsBlockBlock time
{node.nodeDetails.name}{node.blockDetails.height}{node.blockDetails.blockTime / 1000}s
{name}{implementation} v{version}{peers}{txcount}{height}{blockTime / 1000}s
@@ -67,8 +77,8 @@ export default class App extends React.Component<{}, State> { } return; case 'added': { - const [id, nodeDetails, blockDetails] = message.payload; - const node = { nodeDetails, blockDetails }; + const [id, nodeDetails, nodeStats, blockDetails] = message.payload; + const node = { nodeDetails, nodeStats, blockDetails }; nodes.set(id, node); } @@ -89,6 +99,18 @@ export default class App extends React.Component<{}, State> { node.blockDetails = blockDetails; } break; + case 'stats': { + const [id, nodeStats] = message.payload; + + const node = nodes.get(id); + + if (!node) { + return; + } + + node.nodeStats = nodeStats; + } + break; default: return; } diff --git a/scripts/start-backend.sh b/scripts/start-backend.sh index 49f4590..294dcd8 100755 --- a/scripts/start-backend.sh +++ b/scripts/start-backend.sh @@ -1,3 +1,3 @@ scripts/build-common.sh scripts/build-backend.sh -node packages/backend/build/index.js +node packages/backend/build/backend/src/index.js diff --git a/tsconfig.json b/tsconfig.json index bd1837c..263b9a1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,9 +22,6 @@ "typeRoots": [ "./node_modules/@types" ], - "include": [ - "src/**/*.ts" - ], "exclude": [ "node_modules" ]