mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-13 10:21:02 +00:00
Better Sparklines (#72)
* Cleaner renders * Add timestamps to Sparklines * Keep track of chart history up to 1h in the past
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { Maybe, Types, timestamp } from '@dotstats/common';
|
||||
|
||||
export class MeanList<T extends number> {
|
||||
private periodIndex = 0;
|
||||
private period = Array<T>(32).fill(0 as T);
|
||||
private meanIndex = 0;
|
||||
private means = Array<T>(20).fill(0 as T);
|
||||
private ticksPerMean = 1;
|
||||
|
||||
public push(val: T) {
|
||||
this.period[this.periodIndex++] = val;
|
||||
|
||||
if (this.periodIndex === this.ticksPerMean) {
|
||||
this.pushMean();
|
||||
}
|
||||
}
|
||||
|
||||
public get(): Array<T> {
|
||||
if (this.meanIndex === 20) {
|
||||
return this.means;
|
||||
} else {
|
||||
return this.means.slice(0, this.meanIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private pushMean() {
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0; i < this.periodIndex; i++) {
|
||||
sum += this.period[i] as number;
|
||||
}
|
||||
|
||||
const mean = (sum / this.periodIndex) as T;
|
||||
|
||||
if (this.meanIndex === 20) {
|
||||
if (this.ticksPerMean === 32) {
|
||||
this.means.copyWithin(0, 1);
|
||||
this.means[20] = mean;
|
||||
} else {
|
||||
this.squashMeans();
|
||||
this.means[this.meanIndex++] = mean;
|
||||
}
|
||||
} else {
|
||||
this.means[this.meanIndex++] = mean;
|
||||
}
|
||||
|
||||
this.periodIndex = 0;
|
||||
}
|
||||
|
||||
private squashMeans() {
|
||||
this.ticksPerMean *= 2;
|
||||
|
||||
const means = this.means;
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let i2 = i * 2;
|
||||
means[i] = (((means[i2] as number) + (means[i2 + 1] as number)) / 2) as T;
|
||||
}
|
||||
|
||||
this.meanIndex = 10;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { noop, timestamp, Maybe, Types, NumStats } from '@dotstats/common';
|
||||
import { parseMessage, getBestBlock, Message, BestBlock, SystemInterval } from './message';
|
||||
import { locate, Location } from './location';
|
||||
import { getId, refreshId } from './nodeId';
|
||||
import { MeanList } from './MeanList';
|
||||
|
||||
const BLOCK_TIME_HISTORY = 10;
|
||||
const MEMORY_RECORDS = 20;
|
||||
@@ -39,8 +40,9 @@ export default class Node {
|
||||
|
||||
private peers = 0 as Types.PeerCount;
|
||||
private txcount = 0 as Types.TransactionCount;
|
||||
private memory = Array<Types.MemoryUse>();
|
||||
private cpu = Array<Types.CPUUse>();
|
||||
private memory = new MeanList<Types.MemoryUse>();
|
||||
private cpu = new MeanList<Types.CPUUse>();
|
||||
private chartstamps = new MeanList<Types.Timestamp>();
|
||||
|
||||
private readonly ip: string;
|
||||
private readonly socket: WebSocket;
|
||||
@@ -177,7 +179,7 @@ export default class Node {
|
||||
}
|
||||
|
||||
public nodeStats(): Types.NodeStats {
|
||||
return [this.peers, this.txcount, this.memory, this.cpu];
|
||||
return [this.peers, this.txcount, this.memory.get(), this.cpu.get(), this.chartstamps.get()];
|
||||
}
|
||||
|
||||
public blockDetails(): Types.BlockDetails {
|
||||
@@ -232,22 +234,10 @@ export default class Node {
|
||||
this.peers = peers;
|
||||
this.txcount = txcount;
|
||||
|
||||
if (cpu) {
|
||||
if (this.cpu.length === CPU_RECORDS) {
|
||||
this.cpu.copyWithin(0, 1);
|
||||
this.cpu[CPU_RECORDS-1] = cpu;
|
||||
} else {
|
||||
this.cpu.push(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
if (memory) {
|
||||
if (this.memory.length === MEMORY_RECORDS) {
|
||||
this.memory.copyWithin(0, 1);
|
||||
this.memory[MEMORY_RECORDS-1] = memory;
|
||||
} else {
|
||||
this.memory.push(memory);
|
||||
}
|
||||
if (cpu != null && memory != null) {
|
||||
this.cpu.push(cpu);
|
||||
this.memory.push(memory);
|
||||
this.chartstamps.push(timestamp());
|
||||
}
|
||||
|
||||
this.events.emit('stats');
|
||||
|
||||
@@ -8,4 +8,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 = 17 as Types.FeedVersion;
|
||||
export const VERSION: Types.FeedVersion = 18 as Types.FeedVersion;
|
||||
|
||||
@@ -25,5 +25,5 @@ export type CPUUse = Opaque<number, 'CPUUse'>;
|
||||
|
||||
export type BlockDetails = [BlockNumber, BlockHash, Milliseconds, Timestamp, Maybe<PropagationTime>];
|
||||
export type NodeDetails = [NodeName, NodeImplementation, NodeVersion, Maybe<Address>];
|
||||
export type NodeStats = [PeerCount, TransactionCount, Array<MemoryUse>, Array<CPUUse>];
|
||||
export type NodeStats = [PeerCount, TransactionCount, Array<MemoryUse>, Array<CPUUse>, Array<Timestamp>];
|
||||
export type NodeLocation = [Latitude, Longitude, City];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import Identicon from 'polkadot-identicon';
|
||||
import { Types } from '@dotstats/common';
|
||||
import { Types, Maybe, timestamp } from '@dotstats/common';
|
||||
import { formatNumber, milliOrSecond, secondsWithPrecision } from '../../utils';
|
||||
import { State as AppState, Node } from '../../state';
|
||||
import { PersistentSet } from '../../persist';
|
||||
@@ -55,23 +55,37 @@ function Truncate(props: { text: string, position?: 'left' | 'right' | 'center'
|
||||
);
|
||||
}
|
||||
|
||||
function formatMemory(kbs: number): string {
|
||||
function formatStamp(stamp: Types.Timestamp): string {
|
||||
const passed = (timestamp() - stamp) / 1000 | 0;
|
||||
|
||||
const hours = Math.round(passed / 3600);
|
||||
const minutes = Math.round((passed % 3600) / 60);
|
||||
const seconds = (passed % 60) | 0;
|
||||
|
||||
return hours ? `${hours}h ago`
|
||||
: minutes ? `${minutes}m ago`
|
||||
: `${seconds}s ago`;
|
||||
}
|
||||
|
||||
function formatMemory(kbs: number, stamp: Maybe<Types.Timestamp>): string {
|
||||
const ago = stamp ? ` (${formatStamp(stamp)})` : '';
|
||||
const mbs = kbs / 1024 | 0;
|
||||
|
||||
if (mbs >= 1000) {
|
||||
return `${(mbs / 1024).toFixed(1)} GB`;
|
||||
return `${(mbs / 1024).toFixed(1)} GB${ago}`;
|
||||
} else {
|
||||
return `${mbs} MB`;
|
||||
return `${mbs} MB${ago}`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatCPU(cpu: number): string {
|
||||
function formatCPU(cpu: number, stamp: Maybe<Types.Timestamp>): string {
|
||||
const ago = stamp ? ` (${formatStamp(stamp)})` : '';
|
||||
const fractionDigits = cpu > 100 ? 0
|
||||
: cpu > 10 ? 1
|
||||
: cpu > 1 ? 2
|
||||
: 3;
|
||||
|
||||
return `${cpu.toFixed(fractionDigits)}%`;
|
||||
return `${cpu.toFixed(fractionDigits)}%${ago}`;
|
||||
}
|
||||
|
||||
export default class Row extends React.Component<RowProps, {}> {
|
||||
@@ -134,13 +148,13 @@ export default class Row extends React.Component<RowProps, {}> {
|
||||
icon: cpuIcon,
|
||||
width: 40,
|
||||
setting: 'cpu',
|
||||
render: ({ cpu }) => {
|
||||
render: ({ cpu, chartstamps }) => {
|
||||
if (cpu.length < 3) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return (
|
||||
<Sparkline width={48} height={16} stroke={1} format={formatCPU} values={cpu} minScale={100} />
|
||||
<Sparkline width={44} height={16} stroke={1} format={formatCPU} values={cpu} stamps={chartstamps} minScale={100} />
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -149,13 +163,13 @@ export default class Row extends React.Component<RowProps, {}> {
|
||||
icon: memoryIcon,
|
||||
width: 40,
|
||||
setting: 'mem',
|
||||
render: ({ mem }) => {
|
||||
render: ({ mem, chartstamps }) => {
|
||||
if (mem.length < 3) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return (
|
||||
<Sparkline width={48} height={16} stroke={1} format={formatMemory} values={mem} />
|
||||
<Sparkline width={44} height={16} stroke={1} format={formatMemory} values={mem} stamps={chartstamps} />
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
fill: currentcolor; /* rgba(255,255,255,0.5); */
|
||||
fill-opacity: 0.5;
|
||||
stroke: currentcolor;
|
||||
margin: 0 -14px 0 -4px;
|
||||
margin: 0 -1px -3px -1px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Types, Maybe } from "@dotstats/common";
|
||||
import sparkline from "@fnando/sparkline";
|
||||
import { Tooltip } from './';
|
||||
|
||||
@@ -10,8 +11,9 @@ export namespace Sparkline {
|
||||
width: number;
|
||||
height: number;
|
||||
values: number[];
|
||||
stamps?: Types.Timestamp[];
|
||||
minScale?: number;
|
||||
format?: (value: number) => string;
|
||||
format?: (value: number, stamp: Maybe<Types.Timestamp>) => string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +23,8 @@ export class Sparkline extends React.Component<Sparkline.Props, {}> {
|
||||
|
||||
public componentDidMount() {
|
||||
sparkline(this.el, this.props.values, {
|
||||
spotRadius: 0.1,
|
||||
minScale: this.props.minScale,
|
||||
interactive: true,
|
||||
onmousemove: this.onMouseMove,
|
||||
});
|
||||
@@ -35,6 +39,7 @@ export class Sparkline extends React.Component<Sparkline.Props, {}> {
|
||||
|
||||
if (this.props.values !== nextProps.values) {
|
||||
sparkline(this.el, nextProps.values, {
|
||||
spotRadius: 0.1,
|
||||
minScale,
|
||||
interactive: true,
|
||||
onmousemove: this.onMouseMove,
|
||||
@@ -62,9 +67,9 @@ export class Sparkline extends React.Component<Sparkline.Props, {}> {
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
private onMouseMove = (event: MouseEvent, data: { value: number }) => {
|
||||
const { format } = this.props;
|
||||
const str = format ? format(data.value) : `${data.value}`;
|
||||
private onMouseMove = (event: MouseEvent, data: { value: number, index: number }) => {
|
||||
const { format, stamps } = this.props;
|
||||
const str = format ? format(data.value, stamps ? stamps[data.index] : null) : `${data.value}`;
|
||||
this.update(str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ export class Node {
|
||||
public txs: Types.TransactionCount;
|
||||
public mem: Types.MemoryUse[];
|
||||
public cpu: Types.CPUUse[];
|
||||
public chartstamps: Types.Timestamp[];
|
||||
|
||||
public height: Types.BlockNumber;
|
||||
public hash: Types.BlockHash;
|
||||
@@ -67,12 +68,13 @@ export class Node {
|
||||
}
|
||||
|
||||
public updateStats(stats: Types.NodeStats) {
|
||||
const [peers, txs, mem, cpu] = stats;
|
||||
const [peers, txs, mem, cpu, chartstamps] = stats;
|
||||
|
||||
this.peers = peers;
|
||||
this.txs = txs;
|
||||
this.mem = mem;
|
||||
this.cpu = cpu;
|
||||
this.chartstamps = chartstamps;
|
||||
}
|
||||
|
||||
public updateBlock(block: Types.BlockDetails) {
|
||||
|
||||
Reference in New Issue
Block a user