mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-12 03:01:09 +00:00
Display block propagation time
This commit is contained in:
@@ -77,10 +77,13 @@ export default class Chain {
|
||||
if (node.height > this.height) {
|
||||
this.height = node.height;
|
||||
this.blockTimestamp = node.blockTimestamp;
|
||||
node.propagationTime = 0 as Types.PropagationTime;
|
||||
|
||||
this.feeds.broadcast(Feed.bestBlock(this.height, this.blockTimestamp));
|
||||
|
||||
console.log(`[${this.label}] New block ${this.height}`);
|
||||
} else if (node.height === this.height) {
|
||||
node.propagationTime = (node.blockTimestamp - this.blockTimestamp) as Types.PropagationTime;
|
||||
}
|
||||
|
||||
this.feeds.broadcast(Feed.imported(node));
|
||||
|
||||
@@ -25,6 +25,7 @@ export default class Node {
|
||||
public latency = 0 as Types.Milliseconds;
|
||||
public blockTime = 0 as Types.Milliseconds;
|
||||
public blockTimestamp = 0 as Types.Timestamp;
|
||||
public propagationTime: Maybe<Types.PropagationTime> = null;
|
||||
|
||||
private peers = 0 as Types.PeerCount;
|
||||
private txcount = 0 as Types.TransactionCount;
|
||||
@@ -132,7 +133,7 @@ export default class Node {
|
||||
}
|
||||
|
||||
public blockDetails(): Types.BlockDetails {
|
||||
return [this.height, this.best, this.blockTime, this.blockTimestamp];
|
||||
return [this.height, this.best, this.blockTime, this.blockTimestamp, this.propagationTime];
|
||||
}
|
||||
|
||||
public get average(): number {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Opaque } from './helpers';
|
||||
import { Opaque, Maybe } from './helpers';
|
||||
import { Id } from './id';
|
||||
|
||||
export type ChainLabel = Opaque<string, 'ChainLabel'>;
|
||||
@@ -11,9 +11,10 @@ export type BlockNumber = Opaque<number, 'BlockNumber'>;
|
||||
export type BlockHash = Opaque<string, 'BlockHash'>;
|
||||
export type Milliseconds = Opaque<number, 'Milliseconds'>;
|
||||
export type Timestamp = Opaque<Milliseconds, 'Timestamp'>;
|
||||
export type PropagationTime = Opaque<Milliseconds, 'PropagationTime'>;
|
||||
export type PeerCount = Opaque<number, 'PeerCount'>;
|
||||
export type TransactionCount = Opaque<number, 'TransactionCount'>;
|
||||
|
||||
export type BlockDetails = [BlockNumber, BlockHash, Milliseconds, Timestamp];
|
||||
export type BlockDetails = [BlockNumber, BlockHash, Milliseconds, Timestamp, Maybe<PropagationTime>];
|
||||
export type NodeDetails = [NodeName, NodeImplementation, NodeVersion];
|
||||
export type NodeStats = [PeerCount, TransactionCount];
|
||||
|
||||
@@ -3,27 +3,12 @@
|
||||
font-family: Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
width: 100%;
|
||||
background: #eee;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.App-list {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.App-list thead {
|
||||
background: #3c3c3b;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.App-list tbody {
|
||||
font-family: monospace, sans-serif;
|
||||
}
|
||||
|
||||
.App-list th, .App-list td {
|
||||
text-align: left;
|
||||
padding: 0.8em 1em;
|
||||
.App-no-telemetry {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
line-height: 80vh;
|
||||
font-size: 3.5em;
|
||||
font-weight: 100;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { Types } from '@dotstats/common';
|
||||
import { Chains, Node, Icon, Tile, Ago } from './components';
|
||||
import { Chains, Chain, Ago } from './components';
|
||||
import { Connection } from './message';
|
||||
import { State } from './state';
|
||||
import { formatNumber } from './utils';
|
||||
|
||||
import './App.css';
|
||||
import nodeIcon from './icons/server.svg';
|
||||
import nodeTypeIcon from './icons/terminal.svg';
|
||||
import peersIcon from './icons/broadcast.svg';
|
||||
import transactionsIcon from './icons/inbox.svg';
|
||||
import blockIcon from './icons/package.svg';
|
||||
import blockHashIcon from './icons/file-binary.svg';
|
||||
import blockTimeIcon from './icons/history.svg';
|
||||
import lastTimeIcon from './icons/watch.svg';
|
||||
|
||||
export default class App extends React.Component<{}, State> {
|
||||
public state: State = {
|
||||
@@ -40,41 +31,19 @@ export default class App extends React.Component<{}, State> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { best, blockTimestamp, timeDiff, chains, subscribed } = this.state;
|
||||
const { chains, timeDiff, subscribed } = this.state;
|
||||
|
||||
Ago.timeDiff = timeDiff;
|
||||
|
||||
if (chains.size === 0) {
|
||||
return <div className="App App-no-telemetry">Waiting for telemetry data...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<Chains chains={chains} subscribed={subscribed} connection={this.connection} />
|
||||
<div className="App-header">
|
||||
<Tile icon={blockIcon} title="Best Block">#{formatNumber(best)}</Tile>
|
||||
<Tile icon={lastTimeIcon} title="Last Block"><Ago when={blockTimestamp} /></Tile>
|
||||
</div>
|
||||
<table className="App-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><Icon src={nodeIcon} alt="Node" /></th>
|
||||
<th><Icon src={nodeTypeIcon} alt="Implementation" /></th>
|
||||
<th><Icon src={peersIcon} alt="Peer Count" /></th>
|
||||
<th><Icon src={transactionsIcon} alt="Transactions in Queue" /></th>
|
||||
<th><Icon src={blockIcon} alt="Block" /></th>
|
||||
<th><Icon src={blockHashIcon} alt="Block Hash" /></th>
|
||||
<th><Icon src={blockTimeIcon} alt="Block Time" /></th>
|
||||
<th><Icon src={lastTimeIcon} alt="Last Block Time" /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.nodes().map((props) => <Node key={props.id} {...props} />)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<Chain state={this.state} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private nodes(): Node.Props[] {
|
||||
return Array.from(this.state.nodes.values()).sort((a, b) => b.blockDetails[0] - a.blockDetails[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
.Chain-header {
|
||||
width: 100%;
|
||||
background: #eee;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.Chain-content {
|
||||
position: absolute;
|
||||
left: 80px;
|
||||
right: 0;
|
||||
min-height: 50vh;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
box-shadow: rgba(0,0,0,0.5) 0 3px 30px;
|
||||
}
|
||||
|
||||
.Chain-node-list {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.Chain-node-list thead {
|
||||
background: #3c3c3b;
|
||||
}
|
||||
|
||||
.Chain-node-list tbody {
|
||||
font-family: monospace, sans-serif;
|
||||
}
|
||||
|
||||
.Chain-node-list th, .Chain-node-list td {
|
||||
text-align: left;
|
||||
padding: 0.8em 1em;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as React from 'react';
|
||||
import { State } from '../state';
|
||||
import { formatNumber } from '../utils';
|
||||
import { Tile, Icon, Node, Ago } from './';
|
||||
|
||||
import nodeIcon from '../icons/server.svg';
|
||||
import nodeTypeIcon from '../icons/terminal.svg';
|
||||
import peersIcon from '../icons/broadcast.svg';
|
||||
import transactionsIcon from '../icons/inbox.svg';
|
||||
import blockIcon from '../icons/package.svg';
|
||||
import blockHashIcon from '../icons/file-binary.svg';
|
||||
import blockTimeIcon from '../icons/history.svg';
|
||||
import propagationTimeIcon from '../icons/dashboard.svg';
|
||||
import lastTimeIcon from '../icons/watch.svg';
|
||||
|
||||
import './Chain.css';
|
||||
|
||||
export namespace Chain {
|
||||
export interface Props {
|
||||
state: Readonly<State>
|
||||
}
|
||||
}
|
||||
|
||||
function sortNodes(a: Node.Props, b: Node.Props): number {
|
||||
const aPropagation = a.blockDetails[4] == null ? Infinity : a.blockDetails[4] as number;
|
||||
const bPropagation = b.blockDetails[4] == null ? Infinity : b.blockDetails[4] as number;
|
||||
|
||||
return aPropagation - bPropagation;
|
||||
}
|
||||
|
||||
export function Chain(props: Chain.Props) {
|
||||
const { best, blockTimestamp } = props.state;
|
||||
|
||||
const nodes = Array.from(props.state.nodes.values()).sort(sortNodes);
|
||||
|
||||
return (
|
||||
<div className="Chain">
|
||||
<div className="Chain-header">
|
||||
<Tile icon={blockIcon} title="Best Block">#{formatNumber(best)}</Tile>
|
||||
<Tile icon={lastTimeIcon} title="Last Block"><Ago when={blockTimestamp} /></Tile>
|
||||
</div>
|
||||
<div className="Chain-content">
|
||||
<table className="Chain-node-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><Icon src={nodeIcon} alt="Node" /></th>
|
||||
<th><Icon src={nodeTypeIcon} alt="Implementation" /></th>
|
||||
<th><Icon src={peersIcon} alt="Peer Count" /></th>
|
||||
<th><Icon src={transactionsIcon} alt="Transactions in Queue" /></th>
|
||||
<th><Icon src={blockIcon} alt="Block" /></th>
|
||||
<th><Icon src={blockHashIcon} alt="Block Hash" /></th>
|
||||
<th><Icon src={blockTimeIcon} alt="Block Time" /></th>
|
||||
<th><Icon src={propagationTimeIcon} alt="Block Propagation Time" /></th>
|
||||
<th><Icon src={lastTimeIcon} alt="Last Block Time" /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
nodes.map((node) => <Node key={node.id} {...node} />)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export namespace Node {
|
||||
|
||||
export function Node(props: Node.Props) {
|
||||
const [name, implementation, version] = props.nodeDetails;
|
||||
const [height, hash, blockTime, blockTimestamp] = props.blockDetails;
|
||||
const [height, hash, blockTime, blockTimestamp, propagationTime] = props.blockDetails;
|
||||
const [peers, txcount] = props.nodeStats;
|
||||
|
||||
return (
|
||||
@@ -26,6 +26,7 @@ export function Node(props: Node.Props) {
|
||||
<td>#{formatNumber(height)}</td>
|
||||
<td><span title={hash}>{trimHash(hash, 16)}</span></td>
|
||||
<td>{(blockTime / 1000).toFixed(3)}s</td>
|
||||
<td>{propagationTime === null ? '∞' : `${propagationTime}ms`}</td>
|
||||
<td><Ago when={blockTimestamp} /></td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
@@ -9,17 +9,16 @@
|
||||
|
||||
.Tile-label {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
top: 25px;
|
||||
left: 100px;
|
||||
right: 0;
|
||||
font-size: 0.4em;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Tile-content {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
bottom: 25px;
|
||||
left: 100px;
|
||||
right: 0;
|
||||
font-weight: 300;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './Chains';
|
||||
export * from './Chain';
|
||||
export * from './Icon';
|
||||
export * from './Node';
|
||||
export * from './Tile';
|
||||
|
||||
Reference in New Issue
Block a user