mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-25 17:31:03 +00:00
I/O metrics (#224)
* feat: Proxy Node IO stats to the FE * chore: Sync up FE * feat: Charts for cache sizes * feat: All graphs are in * chore: Remove browserlist
This commit is contained in:
@@ -213,7 +213,7 @@ impl Handler<Connect> for Aggregator {
|
||||
|
||||
connector.do_send(Connected(fid));
|
||||
|
||||
self.serializer.push(feed::Version(28));
|
||||
self.serializer.push(feed::Version(29));
|
||||
|
||||
// TODO: keep track on number of nodes connected to each chain
|
||||
for (_, entry) in self.chains.iter() {
|
||||
|
||||
@@ -273,6 +273,10 @@ impl Handler<UpdateNode> for Chain {
|
||||
if let Some(stats) = node.update_stats(interval) {
|
||||
self.serializer.push(feed::NodeStatsUpdate(nid, stats));
|
||||
}
|
||||
|
||||
if let Some(io) = node.update_io(interval) {
|
||||
self.serializer.push(feed::NodeIOUpdate(nid, io));
|
||||
}
|
||||
}
|
||||
Details::SystemNetworkState(_) => {
|
||||
if let Some(raw) = raw {
|
||||
|
||||
+7
-2
@@ -4,7 +4,7 @@ use serde::ser::{Serializer, SerializeTuple};
|
||||
use serde_json::to_writer;
|
||||
use crate::node::Node;
|
||||
use crate::types::{
|
||||
NodeId, NodeStats, NodeHardware, BlockNumber, BlockHash, BlockDetails, Timestamp, Address,
|
||||
NodeId, NodeStats, NodeHardware, NodeIO, BlockNumber, BlockHash, BlockDetails, Timestamp, Address,
|
||||
};
|
||||
|
||||
pub mod connector;
|
||||
@@ -91,6 +91,7 @@ actions! {
|
||||
0x12: AfgReceivedPrecommit,
|
||||
0x13: AfgAuthoritySet,
|
||||
0x14: StaleNode,
|
||||
0x15: NodeIOUpdate<'_>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -119,6 +120,9 @@ pub struct FinalizedBlock(pub NodeId, pub BlockNumber, pub BlockHash);
|
||||
#[derive(Serialize)]
|
||||
pub struct NodeStatsUpdate<'a>(pub NodeId, pub &'a NodeStats);
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct NodeIOUpdate<'a>(pub NodeId, pub &'a NodeIO);
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Hardware<'a>(pub NodeId, pub &'a NodeHardware);
|
||||
|
||||
@@ -161,10 +165,11 @@ impl Serialize for AddedNode<'_> {
|
||||
S: Serializer,
|
||||
{
|
||||
let AddedNode(nid, node) = self;
|
||||
let mut tup = serializer.serialize_tuple(7)?;
|
||||
let mut tup = serializer.serialize_tuple(8)?;
|
||||
tup.serialize_element(nid)?;
|
||||
tup.serialize_element(node.details())?;
|
||||
tup.serialize_element(node.stats())?;
|
||||
tup.serialize_element(node.io())?;
|
||||
tup.serialize_element(node.hardware())?;
|
||||
tup.serialize_element(node.block_details())?;
|
||||
tup.serialize_element(&node.location())?;
|
||||
|
||||
+34
-12
@@ -1,7 +1,7 @@
|
||||
use bytes::Bytes;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::types::{NodeId, NodeDetails, NodeStats, NodeHardware, NodeLocation, BlockDetails, Block, Timestamp};
|
||||
use crate::types::{NodeId, NodeDetails, NodeStats, NodeIO, NodeHardware, NodeLocation, BlockDetails, Block, Timestamp};
|
||||
use crate::util::now;
|
||||
|
||||
pub mod message;
|
||||
@@ -19,6 +19,8 @@ pub struct Node {
|
||||
details: NodeDetails,
|
||||
/// Basic stats
|
||||
stats: NodeStats,
|
||||
/// Node IO stats
|
||||
io: NodeIO,
|
||||
/// Best block
|
||||
best: BlockDetails,
|
||||
/// Finalized block
|
||||
@@ -42,16 +44,9 @@ impl Node {
|
||||
Node {
|
||||
|
||||
details,
|
||||
stats: NodeStats {
|
||||
txcount: 0,
|
||||
peers: 0,
|
||||
},
|
||||
best: BlockDetails {
|
||||
block: Block::zero(),
|
||||
block_timestamp: now(),
|
||||
block_time: 0,
|
||||
propagation_time: None,
|
||||
},
|
||||
stats: NodeStats::default(),
|
||||
io: NodeIO::default(),
|
||||
best: BlockDetails::default(),
|
||||
finalized: Block::zero(),
|
||||
throttle: 0,
|
||||
hardware: NodeHardware::default(),
|
||||
@@ -70,6 +65,10 @@ impl Node {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
pub fn io(&self) -> &NodeIO {
|
||||
&self.io
|
||||
}
|
||||
|
||||
pub fn best(&self) -> &Block {
|
||||
&self.best.block
|
||||
}
|
||||
@@ -105,7 +104,7 @@ impl Node {
|
||||
if block.height > self.best.block.height {
|
||||
self.stale = false;
|
||||
self.best.block = block;
|
||||
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -157,6 +156,29 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_io(&mut self, interval: &SystemInterval) -> Option<&NodeIO> {
|
||||
let mut changed = false;
|
||||
|
||||
if let Some(size) = interval.used_state_cache_size {
|
||||
changed |= self.io.used_state_cache_size.push(size);
|
||||
}
|
||||
if let Some(size) = interval.used_db_cache_size {
|
||||
changed |= self.io.used_db_cache_size.push(size);
|
||||
}
|
||||
if let Some(bps) = interval.disk_read_per_sec {
|
||||
changed |= self.io.disk_read_per_sec.push(bps);
|
||||
}
|
||||
if let Some(bps) = interval.disk_write_per_sec {
|
||||
changed |= self.io.disk_write_per_sec.push(bps);
|
||||
}
|
||||
|
||||
if changed {
|
||||
Some(&self.io)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_finalized(&mut self, block: Block) -> Option<&Block> {
|
||||
if block.height > self.finalized.height {
|
||||
self.finalized = block;
|
||||
|
||||
@@ -74,6 +74,10 @@ pub struct SystemInterval {
|
||||
#[serde(flatten)]
|
||||
pub block: Block,
|
||||
pub network_state: Option<IgnoredAny>,
|
||||
pub used_state_cache_size: Option<f32>,
|
||||
pub used_db_cache_size: Option<f32>,
|
||||
pub disk_read_per_sec: Option<f32>,
|
||||
pub disk_write_per_sec: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
||||
+35
-2
@@ -1,7 +1,7 @@
|
||||
use serde::ser::{Serialize, Serializer, SerializeTuple};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::util::MeanList;
|
||||
use crate::util::{MeanList, now};
|
||||
|
||||
pub type NodeId = usize;
|
||||
pub type BlockNumber = u64;
|
||||
@@ -18,12 +18,20 @@ pub struct NodeDetails {
|
||||
pub network_id: Option<Box<str>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct NodeStats {
|
||||
pub peers: u64,
|
||||
pub txcount: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NodeIO {
|
||||
pub used_state_cache_size: MeanList<f32>,
|
||||
pub used_db_cache_size: MeanList<f32>,
|
||||
pub disk_read_per_sec: MeanList<f32>,
|
||||
pub disk_write_per_sec: MeanList<f32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Copy)]
|
||||
pub struct Block {
|
||||
#[serde(rename = "best")]
|
||||
@@ -39,6 +47,17 @@ pub struct BlockDetails {
|
||||
pub propagation_time: Option<u64>,
|
||||
}
|
||||
|
||||
impl Default for BlockDetails {
|
||||
fn default() -> Self {
|
||||
BlockDetails {
|
||||
block: Block::zero(),
|
||||
block_timestamp: now(),
|
||||
block_time: 0,
|
||||
propagation_time: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NodeHardware {
|
||||
/// CPU use means
|
||||
@@ -87,6 +106,20 @@ impl Serialize for NodeStats {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for NodeIO {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut tup = serializer.serialize_tuple(4)?;
|
||||
tup.serialize_element(self.used_state_cache_size.slice())?;
|
||||
tup.serialize_element(self.used_db_cache_size.slice())?;
|
||||
tup.serialize_element(self.disk_read_per_sec.slice())?;
|
||||
tup.serialize_element(self.disk_write_per_sec.slice())?;
|
||||
tup.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for BlockDetails {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
NodeCount,
|
||||
NodeDetails,
|
||||
NodeStats,
|
||||
NodeIO,
|
||||
NodeHardware,
|
||||
NodeLocation,
|
||||
BlockNumber,
|
||||
@@ -43,6 +44,7 @@ export const Actions = {
|
||||
AfgReceivedPrecommit : 0x12 as 0x12,
|
||||
AfgAuthoritySet : 0x13 as 0x13,
|
||||
StaleNode : 0x14 as 0x14,
|
||||
NodeIO : 0x15 as 0x15,
|
||||
};
|
||||
|
||||
export type Action = typeof Actions[keyof typeof Actions];
|
||||
@@ -70,7 +72,7 @@ export namespace Variants {
|
||||
|
||||
export interface AddedNodeMessage extends MessageBase {
|
||||
action: typeof Actions.AddedNode;
|
||||
payload: [NodeId, NodeDetails, NodeStats, NodeHardware, BlockDetails, Maybe<NodeLocation>, Timestamp];
|
||||
payload: [NodeId, NodeDetails, NodeStats, NodeIO, NodeHardware, BlockDetails, Maybe<NodeLocation>, Timestamp];
|
||||
}
|
||||
|
||||
export interface RemovedNodeMessage extends MessageBase {
|
||||
@@ -103,6 +105,11 @@ export namespace Variants {
|
||||
payload: [NodeId, NodeHardware];
|
||||
}
|
||||
|
||||
export interface NodeIOMessage extends MessageBase {
|
||||
action: typeof Actions.NodeIO;
|
||||
payload: [NodeId, NodeIO];
|
||||
}
|
||||
|
||||
export interface TimeSyncMessage extends MessageBase {
|
||||
action: typeof Actions.TimeSync;
|
||||
payload: Timestamp;
|
||||
|
||||
@@ -9,4 +9,4 @@ import * as FeedMessage from './feed';
|
||||
export { Types, FeedMessage };
|
||||
|
||||
// Increment this if breaking changes were made to types in `feed.ts`
|
||||
export const VERSION: Types.FeedVersion = 28 as Types.FeedVersion;
|
||||
export const VERSION: Types.FeedVersion = 29 as Types.FeedVersion;
|
||||
|
||||
@@ -22,6 +22,7 @@ export type Longitude = Opaque<number, 'Longitude'>;
|
||||
export type City = Opaque<string, 'City'>;
|
||||
export type MemoryUse = Opaque<number, 'MemoryUse'>;
|
||||
export type CPUUse = Opaque<number, 'CPUUse'>;
|
||||
export type Bytes = Opaque<number, 'Bytes'>;
|
||||
export type BytesPerSecond = Opaque<number, 'BytesPerSecond'>;
|
||||
export type NetworkId = Opaque<string, 'NetworkId'>;
|
||||
export type NetworkState = Opaque<string | object, 'NetworkState'>;
|
||||
@@ -29,6 +30,7 @@ export type NetworkState = Opaque<string | object, 'NetworkState'>;
|
||||
export type BlockDetails = [BlockNumber, BlockHash, Milliseconds, Timestamp, Maybe<PropagationTime>];
|
||||
export type NodeDetails = [NodeName, NodeImplementation, NodeVersion, Maybe<Address>, Maybe<NetworkId>];
|
||||
export type NodeStats = [PeerCount, TransactionCount];
|
||||
export type NodeIO = [Array<Bytes>, Array<Bytes>, Array<BytesPerSecond>, Array<BytesPerSecond>];
|
||||
export type NodeHardware = [Array<MemoryUse>, Array<CPUUse>, Array<BytesPerSecond>, Array<BytesPerSecond>, Array<Timestamp>];
|
||||
export type NodeLocation = [Latitude, Longitude, City];
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ export default class App extends React.Component<{}, State> {
|
||||
mem: true,
|
||||
upload: false,
|
||||
download: false,
|
||||
stateCacheSize: false,
|
||||
dbCacheSize: false,
|
||||
diskRead: false,
|
||||
diskWrite: false,
|
||||
blocknumber: true,
|
||||
blockhash: true,
|
||||
blocktime: true,
|
||||
|
||||
@@ -166,9 +166,9 @@ export class Connection {
|
||||
}
|
||||
|
||||
case Actions.AddedNode: {
|
||||
const [id, nodeDetails, nodeStats, nodeHardware, blockDetails, location, connectedAt] = message.payload;
|
||||
const [id, nodeDetails, nodeStats, nodeIO, nodeHardware, blockDetails, location, connectedAt] = message.payload;
|
||||
const pinned = this.pins.has(nodeDetails[0]);
|
||||
const node = new Node(pinned, id, nodeDetails, nodeStats, nodeHardware, blockDetails, location, connectedAt);
|
||||
const node = new Node(pinned, id, nodeDetails, nodeStats, nodeIO, nodeHardware, blockDetails, location, connectedAt);
|
||||
|
||||
nodes.add(node);
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ import cpuIcon from '../../icons/microchip-solid.svg';
|
||||
import memoryIcon from '../../icons/memory-solid.svg';
|
||||
import uploadIcon from '../../icons/cloud-upload.svg';
|
||||
import downloadIcon from '../../icons/cloud-download.svg';
|
||||
import readIcon from '../../icons/arrow-up.svg';
|
||||
import writeIcon from '../../icons/arrow-down.svg';
|
||||
import databaseIcon from '../../icons/database.svg';
|
||||
import stateIcon from '../../icons/git-branch.svg';
|
||||
import networkIcon from '../../icons/network.svg';
|
||||
import uptimeIcon from '../../icons/pulse.svg';
|
||||
import externalLinkIcon from '../../icons/link-external.svg';
|
||||
@@ -210,6 +214,74 @@ export namespace Column {
|
||||
}
|
||||
};
|
||||
|
||||
export const STATE_CACHE: Column = {
|
||||
label: 'State Cache Size',
|
||||
icon: stateIcon,
|
||||
width: 40,
|
||||
setting: 'stateCacheSize',
|
||||
sortBy: ({ stateCacheSize }) => stateCacheSize.length < 3 ? 0 : stateCacheSize[stateCacheSize.length - 1],
|
||||
render: ({ stateCacheSize, chartstamps }) => {
|
||||
if (stateCacheSize.length < 3) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return (
|
||||
<Sparkline width={44} height={16} stroke={1} format={formatBytes} values={stateCacheSize} stamps={chartstamps} minScale={MEMORY_SCALE} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const DB_CACHE: Column = {
|
||||
label: 'Database Cache Size',
|
||||
icon: databaseIcon,
|
||||
width: 40,
|
||||
setting: 'dbCacheSize',
|
||||
sortBy: ({ dbCacheSize }) => dbCacheSize.length < 3 ? 0 : dbCacheSize[dbCacheSize.length - 1],
|
||||
render: ({ dbCacheSize, chartstamps }) => {
|
||||
if (dbCacheSize.length < 3) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return (
|
||||
<Sparkline width={44} height={16} stroke={1} format={formatBytes} values={dbCacheSize} stamps={chartstamps} minScale={MEMORY_SCALE} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const DISK_READ: Column = {
|
||||
label: 'Disk Read',
|
||||
icon: readIcon,
|
||||
width: 40,
|
||||
setting: 'diskRead',
|
||||
sortBy: ({ diskRead }) => diskRead.length < 3 ? 0 : diskRead[diskRead.length - 1],
|
||||
render: ({ diskRead, chartstamps }) => {
|
||||
if (diskRead.length < 3) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return (
|
||||
<Sparkline width={44} height={16} stroke={1} format={formatBandwidth} values={diskRead} stamps={chartstamps} minScale={MEMORY_SCALE} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const DISK_WRITE: Column = {
|
||||
label: 'Disk Write',
|
||||
icon: writeIcon,
|
||||
width: 40,
|
||||
setting: 'diskWrite',
|
||||
sortBy: ({ diskWrite }) => diskWrite.length < 3 ? 0 : diskWrite[diskWrite.length - 1],
|
||||
render: ({ diskWrite, chartstamps }) => {
|
||||
if (diskWrite.length < 3) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return (
|
||||
<Sparkline width={44} height={16} stroke={1} format={formatBandwidth} values={diskWrite} stamps={chartstamps} minScale={MEMORY_SCALE} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const BLOCK_NUMBER: Column = {
|
||||
label: 'Block',
|
||||
icon: blockIcon,
|
||||
@@ -330,6 +402,18 @@ function formatMemory(kbs: number, stamp: Maybe<Types.Timestamp>): string {
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number, stamp: Maybe<Types.Timestamp>): string {
|
||||
const ago = stamp ? ` (${formatStamp(stamp)})` : '';
|
||||
|
||||
if (bytes >= 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB${ago}`;
|
||||
} else if (bytes >= 1000) {
|
||||
return `${(bytes / 1024).toFixed(1)} kB${ago}`;
|
||||
} else {
|
||||
return `${bytes} B${ago}`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatBandwidth(bps: number, stamp: Maybe<Types.Timestamp>): string {
|
||||
const ago = stamp ? ` (${formatStamp(stamp)})` : '';
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ export class Row extends React.Component<Row.Props, Row.State> {
|
||||
Column.MEM,
|
||||
Column.UPLOAD,
|
||||
Column.DOWNLOAD,
|
||||
Column.STATE_CACHE,
|
||||
Column.DB_CACHE,
|
||||
Column.DISK_READ,
|
||||
Column.DISK_WRITE,
|
||||
Column.BLOCK_NUMBER,
|
||||
Column.BLOCK_HASH,
|
||||
Column.FINALIZED,
|
||||
|
||||
@@ -43,6 +43,10 @@ export class Node {
|
||||
public cpu: Types.CPUUse[];
|
||||
public upload: Types.BytesPerSecond[];
|
||||
public download: Types.BytesPerSecond[];
|
||||
public stateCacheSize: Types.Bytes[];
|
||||
public dbCacheSize: Types.Bytes[];
|
||||
public diskRead: Types.BytesPerSecond[];
|
||||
public diskWrite: Types.BytesPerSecond[];
|
||||
public chartstamps: Types.Timestamp[];
|
||||
|
||||
public height: Types.BlockNumber;
|
||||
@@ -66,6 +70,7 @@ export class Node {
|
||||
id: Types.NodeId,
|
||||
nodeDetails: Types.NodeDetails,
|
||||
nodeStats: Types.NodeStats,
|
||||
nodeIO: Types.NodeIO,
|
||||
nodeHardware: Types.NodeHardware,
|
||||
blockDetails: Types.BlockDetails,
|
||||
location: Maybe<Types.NodeLocation>,
|
||||
@@ -89,6 +94,7 @@ export class Node {
|
||||
this.sortableVersion = (major * 1000 + minor * 100 + patch) | 0;
|
||||
|
||||
this.updateStats(nodeStats);
|
||||
this.updateIO(nodeIO);
|
||||
this.updateHardware(nodeHardware);
|
||||
this.updateBlock(blockDetails);
|
||||
|
||||
@@ -106,6 +112,17 @@ export class Node {
|
||||
this.trigger();
|
||||
}
|
||||
|
||||
public updateIO(io: Types.NodeIO) {
|
||||
const [stateCacheSize, dbCacheSize, diskRead, diskWrite] = io;
|
||||
|
||||
this.stateCacheSize = stateCacheSize;
|
||||
this.dbCacheSize = dbCacheSize;
|
||||
this.diskRead = diskRead;
|
||||
this.diskWrite = diskWrite;
|
||||
|
||||
this.trigger();
|
||||
}
|
||||
|
||||
public updateHardware(hardware: Types.NodeHardware) {
|
||||
const [mem, cpu, upload, download, chartstamps] = hardware;
|
||||
|
||||
@@ -202,6 +219,10 @@ export namespace State {
|
||||
mem: boolean;
|
||||
upload: boolean;
|
||||
download: boolean;
|
||||
stateCacheSize: boolean;
|
||||
dbCacheSize: boolean;
|
||||
diskRead: boolean;
|
||||
diskWrite: boolean;
|
||||
blocknumber: boolean;
|
||||
blockhash: boolean;
|
||||
finalized: boolean;
|
||||
|
||||
Reference in New Issue
Block a user