mirror of
https://github.com/pezkuwichain/pezkuwi-telemetry.git
synced 2026-06-13 06:51:06 +00:00
Add per-chain aggregate software/hardware telemetry (#464)
* Add per-chain aggregate software/hardware telemetry * Fix tests' compilation * Add extra comments for the `Counter` struct * Replace the boolean argument with an enum * Rename `replace_hwbench` to `update_hwbench` * Move `Counter` into a separate file * Move `ChainStatsCollator` to `chain_stats.rs` * Fix incorrect key on the unknown table * Improve types for the stats component; get rid of `any`
This commit is contained in:
@@ -112,6 +112,7 @@ export default class App extends React.Component<{}, {}> {
|
||||
sortBy: this.sortBy.get(),
|
||||
selectedColumns: this.selectedColumns(this.settings.raw()),
|
||||
tab,
|
||||
chainStats: null,
|
||||
});
|
||||
this.appState = this.appUpdate({});
|
||||
|
||||
|
||||
@@ -362,6 +362,11 @@ export class Connection {
|
||||
break;
|
||||
}
|
||||
|
||||
case ACTIONS.ChainStatsUpdate: {
|
||||
this.appUpdate({ chainStats: message.payload });
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
ChainLabel,
|
||||
GenesisHash,
|
||||
AuthoritySetInfo,
|
||||
ChainStats,
|
||||
} from './types';
|
||||
|
||||
export const ACTIONS = {
|
||||
@@ -62,6 +63,7 @@ export const ACTIONS = {
|
||||
AfgAuthoritySet: 0x13 as 0x13,
|
||||
StaleNode: 0x14 as 0x14,
|
||||
NodeIO: 0x15 as 0x15,
|
||||
ChainStatsUpdate: 0x16 as 0x16,
|
||||
};
|
||||
|
||||
export type Action = typeof ACTIONS[keyof typeof ACTIONS];
|
||||
@@ -190,6 +192,11 @@ export namespace Variants {
|
||||
action: typeof ACTIONS.StaleNode;
|
||||
payload: NodeId;
|
||||
}
|
||||
|
||||
export interface ChainStatsUpdate extends MessageBase {
|
||||
action: typeof ACTIONS.ChainStatsUpdate;
|
||||
payload: ChainStats;
|
||||
}
|
||||
}
|
||||
|
||||
export type Message =
|
||||
@@ -214,7 +221,8 @@ export type Message =
|
||||
| Variants.AfgAuthoritySet
|
||||
| Variants.StaleNodeMessage
|
||||
| Variants.PongMessage
|
||||
| Variants.NodeIOMessage;
|
||||
| Variants.NodeIOMessage
|
||||
| Variants.ChainStatsUpdate;
|
||||
|
||||
/**
|
||||
* Data type to be sent to the feed. Passing through strings means we can only serialize once,
|
||||
|
||||
@@ -87,3 +87,27 @@ export declare type ImplicitPrecommit = Opaque<boolean, 'ImplicitPrecommit'>;
|
||||
export declare type ImplicitPrevote = Opaque<boolean, 'ImplicitPrevote'>;
|
||||
export declare type ImplicitFinalized = Opaque<boolean, 'ImplicitFinalized'>;
|
||||
export declare type ImplicitPointer = Opaque<BlockNumber, 'ImplicitPointer'>;
|
||||
|
||||
export type Ranking<T> = {
|
||||
list: Array<[T, number]>;
|
||||
other: number;
|
||||
unknown: number;
|
||||
};
|
||||
|
||||
export type Range = [number, number | null];
|
||||
|
||||
export type ChainStats = {
|
||||
version: Maybe<Ranking<string>>;
|
||||
target_os: Maybe<Ranking<string>>;
|
||||
target_arch: Maybe<Ranking<string>>;
|
||||
cpu: Maybe<Ranking<string>>;
|
||||
core_count: Maybe<Ranking<number>>;
|
||||
memory: Maybe<Ranking<Range>>;
|
||||
is_virtual_machine: Maybe<Ranking<boolean>>;
|
||||
linux_distro: Maybe<Ranking<string>>;
|
||||
linux_kernel: Maybe<Ranking<string>>;
|
||||
cpu_hashrate_score: Maybe<Ranking<Range>>;
|
||||
memory_memcpy_score: Maybe<Ranking<Range>>;
|
||||
disk_sequential_write_score: Maybe<Ranking<Range>>;
|
||||
disk_random_write_score: Maybe<Ranking<Range>>;
|
||||
};
|
||||
|
||||
@@ -20,13 +20,13 @@ import { Types, Maybe } from '../../common';
|
||||
import { State as AppState, Update as AppUpdate } from '../../state';
|
||||
import { getHashData } from '../../utils';
|
||||
import { Header } from './';
|
||||
import { List, Map, Settings } from '../';
|
||||
import { List, Map, Settings, Stats } from '../';
|
||||
import { Persistent, PersistentObject, PersistentSet } from '../../persist';
|
||||
|
||||
import './Chain.css';
|
||||
|
||||
export namespace Chain {
|
||||
export type Display = 'list' | 'map' | 'settings' | 'consensus';
|
||||
export type Display = 'list' | 'map' | 'settings' | 'consensus' | 'stats';
|
||||
|
||||
export interface Props {
|
||||
appState: Readonly<AppState>;
|
||||
@@ -93,16 +93,26 @@ export class Chain extends React.Component<Chain.Props, Chain.State> {
|
||||
|
||||
const { appState, appUpdate, connection, pins, sortBy } = this.props;
|
||||
|
||||
return display === 'list' ? (
|
||||
<List
|
||||
appState={appState}
|
||||
appUpdate={appUpdate}
|
||||
pins={pins}
|
||||
sortBy={sortBy}
|
||||
/>
|
||||
) : (
|
||||
<Map appState={appState} />
|
||||
);
|
||||
if (display === 'list') {
|
||||
return (
|
||||
<List
|
||||
appState={appState}
|
||||
appUpdate={appUpdate}
|
||||
pins={pins}
|
||||
sortBy={sortBy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (display === 'map') {
|
||||
return <Map appState={appState} />;
|
||||
}
|
||||
|
||||
if (display === 'stats') {
|
||||
return <Stats appState={appState} />;
|
||||
}
|
||||
|
||||
throw new Error('invalid `display`: ${display}');
|
||||
}
|
||||
|
||||
private setDisplay = (display: Chain.Display) => {
|
||||
|
||||
@@ -28,6 +28,7 @@ import listIcon from '../../icons/list-alt-regular.svg';
|
||||
import worldIcon from '../../icons/location.svg';
|
||||
import settingsIcon from '../../icons/settings.svg';
|
||||
import consensusIcon from '../../icons/cube-alt.svg';
|
||||
import statsIcon from '../../icons/graph.svg';
|
||||
|
||||
import './Header.css';
|
||||
|
||||
@@ -90,6 +91,14 @@ export class Header extends React.Component<Header.Props, {}> {
|
||||
current={currentTab}
|
||||
setDisplay={setDisplay}
|
||||
/>
|
||||
<Tab
|
||||
icon={statsIcon}
|
||||
label="Stats"
|
||||
display="stats"
|
||||
tab="stats"
|
||||
current={currentTab}
|
||||
setDisplay={setDisplay}
|
||||
/>
|
||||
<Tab
|
||||
icon={settingsIcon}
|
||||
label="Settings"
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Source code for the Substrate Telemetry Server.
|
||||
Copyright (C) 2022 Parity Technologies (UK) Ltd.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.Stats {
|
||||
text-align: center;
|
||||
padding-top: 2.5rem;
|
||||
padding-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.Stats-category {
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
margin-bottom: 2.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.Stats-category table {
|
||||
color: #000;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.Stats-category tr:nth-child(even) {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.Stats-percent {
|
||||
width: 6em;
|
||||
text-align: right;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.Stats-count {
|
||||
width: 6.5em;
|
||||
text-align: right;
|
||||
padding-right: 1.5rem;
|
||||
border-right: 1px solid black;
|
||||
}
|
||||
|
||||
.Stats-value {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
th.Stats-value {
|
||||
padding-left: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.Stats-category td {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.Stats-unknown {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// Source code for the Substrate Telemetry Server.
|
||||
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from 'react';
|
||||
import { Maybe } from '../../common';
|
||||
import { State as AppState } from '../../state';
|
||||
import { Row } from '../List';
|
||||
import { PersistentObject } from '../../persist';
|
||||
import { Ranking, Range } from '../../common/types';
|
||||
|
||||
import './Stats.css';
|
||||
|
||||
export namespace Stats {
|
||||
export type Display = 'list' | 'map' | 'Stats';
|
||||
|
||||
export interface Props {
|
||||
appState: Readonly<AppState>;
|
||||
}
|
||||
}
|
||||
|
||||
function displayPercentage(percent: number): string {
|
||||
return (Math.round(percent * 100) / 100).toFixed(2);
|
||||
}
|
||||
|
||||
function generateRankingTable<T>(
|
||||
key: string,
|
||||
label: string,
|
||||
format: (value: T) => string,
|
||||
ranking: Ranking<T>
|
||||
) {
|
||||
let total = ranking.other + ranking.unknown;
|
||||
ranking.list.forEach(([_, count]) => {
|
||||
total += count;
|
||||
});
|
||||
|
||||
if (ranking.unknown === total) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entries: React.ReactNode[] = [];
|
||||
ranking.list.forEach(([value, count]) => {
|
||||
const percent = displayPercentage((count / total) * 100);
|
||||
const index = entries.length;
|
||||
entries.push(
|
||||
<tr key={index}>
|
||||
<td className="Stats-percent">{percent}%</td>
|
||||
<td className="Stats-count">{count}</td>
|
||||
<td className="Stats-value">{format(value)}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
if (ranking.other > 0) {
|
||||
const percent = displayPercentage((ranking.other / total) * 100);
|
||||
entries.push(
|
||||
<tr key="other">
|
||||
<td className="Stats-percent">{percent}%</td>
|
||||
<td className="Stats-count">{ranking.other}</td>
|
||||
<td className="Stats-value">Other</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
if (ranking.unknown > 0) {
|
||||
const percent = displayPercentage((ranking.unknown / total) * 100);
|
||||
entries.push(
|
||||
<tr key="unknown">
|
||||
<td className="Stats-percent">{percent}%</td>
|
||||
<td className="Stats-count">{ranking.unknown}</td>
|
||||
<td className="Stats-value Stats-unknown">Unknown</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Stats-category" key={key}>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="Stats-percent" />
|
||||
<th className="Stats-count" />
|
||||
<th className="Stats-value">{label}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{entries}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function identity(value: string | number): string {
|
||||
return value + '';
|
||||
}
|
||||
|
||||
function formatMemory(value: Range): string {
|
||||
const [min, max] = value;
|
||||
if (min === 0) {
|
||||
return 'Less than ' + max + ' GB';
|
||||
}
|
||||
if (max === null) {
|
||||
return 'At least ' + min + ' GB';
|
||||
}
|
||||
return min + ' GB';
|
||||
}
|
||||
|
||||
function formatYesNo(value: boolean): string {
|
||||
if (value) {
|
||||
return 'Yes';
|
||||
} else {
|
||||
return 'No';
|
||||
}
|
||||
}
|
||||
|
||||
function formatScore(value: Range): string {
|
||||
const [min, max] = value;
|
||||
if (max === null) {
|
||||
return 'More than ' + (min / 100).toFixed(1) + 'x';
|
||||
}
|
||||
if (min === 0) {
|
||||
return 'Less than ' + (max / 100).toFixed(1) + 'x';
|
||||
}
|
||||
if (min <= 100 && max >= 100) {
|
||||
return 'Baseline';
|
||||
}
|
||||
return (min / 100).toFixed(1) + 'x';
|
||||
}
|
||||
|
||||
export class Stats extends React.Component<Stats.Props, {}> {
|
||||
public render() {
|
||||
const { appState } = this.props;
|
||||
|
||||
const children: React.ReactNode[] = [];
|
||||
function add<T>(
|
||||
key: string,
|
||||
label: string,
|
||||
format: (value: T) => string,
|
||||
ranking: Maybe<Ranking<T>>
|
||||
) {
|
||||
if (ranking) {
|
||||
const child = generateRankingTable(key, label, format, ranking);
|
||||
if (child !== null) {
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stats = appState.chainStats;
|
||||
if (stats) {
|
||||
add('version', 'Version', identity, stats.version);
|
||||
add('target_os', 'Operating System', identity, stats.target_os);
|
||||
add('target_arch', 'CPU Architecture', identity, stats.target_arch);
|
||||
add('cpu', 'CPU', identity, stats.cpu);
|
||||
add('core_count', 'CPU Cores', identity, stats.core_count);
|
||||
add('memory', 'Memory', formatMemory, stats.memory);
|
||||
add(
|
||||
'is_virtual_machine',
|
||||
'Is Virtual Machine?',
|
||||
formatYesNo,
|
||||
stats.is_virtual_machine
|
||||
);
|
||||
add('linux_distro', 'Linux Distribution', identity, stats.linux_distro);
|
||||
add('linux_kernel', 'Linux Kernel', identity, stats.linux_kernel);
|
||||
add(
|
||||
'cpu_hashrate_score',
|
||||
'CPU Speed',
|
||||
formatScore,
|
||||
stats.cpu_hashrate_score
|
||||
);
|
||||
add(
|
||||
'memory_memcpy_score',
|
||||
'Memory Speed',
|
||||
formatScore,
|
||||
stats.memory_memcpy_score
|
||||
);
|
||||
add(
|
||||
'disk_sequential_write_score',
|
||||
'Disk Speed (sequential writes)',
|
||||
formatScore,
|
||||
stats.disk_sequential_write_score
|
||||
);
|
||||
add(
|
||||
'disk_random_write_score',
|
||||
'Disk Speed (random writes)',
|
||||
formatScore,
|
||||
stats.disk_random_write_score
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="Stats">{children}</div>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Source code for the Substrate Telemetry Server.
|
||||
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
export * from './Stats';
|
||||
@@ -20,6 +20,7 @@ export * from './Chain';
|
||||
export * from './List';
|
||||
export * from './Map';
|
||||
export * from './Settings';
|
||||
export * from './Stats';
|
||||
export * from './Icon';
|
||||
export * from './Tile';
|
||||
export * from './Ago';
|
||||
|
||||
@@ -279,6 +279,7 @@ export interface State {
|
||||
pins: Readonly<Set<Types.NodeName>>;
|
||||
sortBy: Readonly<Maybe<number>>;
|
||||
selectedColumns: Column[];
|
||||
chainStats: Maybe<Types.ChainStats>;
|
||||
}
|
||||
|
||||
export type Update = <K extends keyof State>(
|
||||
|
||||
Reference in New Issue
Block a user