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:
Koute
2022-04-27 18:44:34 +09:00
committed by GitHub
parent 978c070bdd
commit 45878f9876
22 changed files with 1034 additions and 18 deletions
+13
View File
@@ -60,6 +60,7 @@ pub enum Payload {
BlockImport(Block),
NotifyFinalized(Finalized),
AfgAuthoritySet(AfgAuthoritySet),
HwBench(NodeHwBench),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -93,6 +94,14 @@ pub struct AfgAuthoritySet {
pub authority_set_id: Box<str>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NodeHwBench {
pub cpu_hashrate_score: u64,
pub memory_memcpy_score: u64,
pub disk_sequential_write_score: Option<u64>,
pub disk_random_write_score: Option<u64>,
}
impl Payload {
pub fn best_block(&self) -> Option<&Block> {
match self {
@@ -145,9 +154,13 @@ mod tests {
name: "foo".into(),
implementation: "foo".into(),
version: "foo".into(),
target_arch: Some("x86_64".into()),
target_os: Some("linux".into()),
target_env: Some("env".into()),
validator: None,
network_id: ArrayString::new(),
startup_time: None,
sysinfo: None,
},
}),
});
+34
View File
@@ -38,6 +38,40 @@ pub struct NodeDetails {
pub validator: Option<Box<str>>,
pub network_id: NetworkId,
pub startup_time: Option<Box<str>>,
pub target_os: Option<Box<str>>,
pub target_arch: Option<Box<str>>,
pub target_env: Option<Box<str>>,
pub sysinfo: Option<NodeSysInfo>,
}
/// Hardware and software information for the node.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NodeSysInfo {
/// The exact CPU model.
pub cpu: Option<Box<str>>,
/// The total amount of memory, in bytes.
pub memory: Option<u64>,
/// The number of physical CPU cores.
pub core_count: Option<u32>,
/// The Linux kernel version.
pub linux_kernel: Option<Box<str>>,
/// The exact Linux distribution used.
pub linux_distro: Option<Box<str>>,
/// Whether the node's running under a virtual machine.
pub is_virtual_machine: Option<bool>,
}
/// Hardware benchmark results for the node.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NodeHwBench {
/// The CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256 hash.
pub cpu_hashrate_score: u64,
/// Memory bandwidth in MB/s, calculated by measuring the throughput of `memcpy`.
pub memory_memcpy_score: u64,
/// Sequential disk write speed in MB/s.
pub disk_sequential_write_score: Option<u64>,
/// Random disk write speed in MB/s.
pub disk_random_write_score: Option<u64>,
}
/// A couple of node statistics.
@@ -514,6 +514,7 @@ impl InnerLoop {
new_chain.finalized_block().height,
new_chain.finalized_block().hash,
));
feed_serializer.push(feed_message::ChainStatsUpdate(new_chain.stats()));
if let Some(bytes) = feed_serializer.into_finalized() {
let _ = feed_channel.send(ToFeedWebsocket::Bytes(bytes));
}
@@ -122,6 +122,7 @@ actions! {
// We maintain existing IDs for backward compatibility.
20: StaleNode,
21: NodeIOUpdate<'_>,
22: ChainStatsUpdate<'_>,
}
#[derive(Serialize)]
@@ -202,3 +203,30 @@ impl FeedMessageWrite for AddedNode<'_> {
));
}
}
#[derive(Serialize)]
pub struct ChainStatsUpdate<'a>(pub &'a ChainStats);
#[derive(Serialize, PartialEq, Eq, Default)]
pub struct Ranking<K> {
pub list: Vec<(K, u64)>,
pub other: u64,
pub unknown: u64,
}
#[derive(Serialize, PartialEq, Eq, Default)]
pub struct ChainStats {
pub version: Ranking<String>,
pub target_os: Ranking<String>,
pub target_arch: Ranking<String>,
pub cpu: Ranking<String>,
pub memory: Ranking<(u32, Option<u32>)>,
pub core_count: Ranking<u32>,
pub linux_kernel: Ranking<String>,
pub linux_distro: Ranking<String>,
pub is_virtual_machine: Ranking<bool>,
pub cpu_hashrate_score: Ranking<(u32, Option<u32>)>,
pub memory_memcpy_score: Ranking<(u32, Option<u32>)>,
pub disk_sequential_write_score: Ranking<(u32, Option<u32>)>,
pub disk_random_write_score: Ranking<(u32, Option<u32>)>,
}
+55 -2
View File
@@ -21,10 +21,13 @@ use common::{id_type, time, DenseMap, MostSeen, NumStats};
use once_cell::sync::Lazy;
use std::collections::HashSet;
use std::str::FromStr;
use std::time::{Duration, Instant};
use crate::feed_message::{self, FeedMessageSerializer};
use crate::feed_message::{self, ChainStats, FeedMessageSerializer};
use crate::find_location;
use super::chain_stats::ChainStatsCollator;
use super::counter::CounterValue;
use super::node::Node;
id_type! {
@@ -35,6 +38,7 @@ id_type! {
pub type Label = Box<str>;
const STALE_TIMEOUT: u64 = 2 * 60 * 1000; // 2 minutes
const STATS_UPDATE_INTERVAL: Duration = Duration::from_secs(5);
pub struct Chain {
/// Labels that nodes use for this chain. We keep track of
@@ -56,6 +60,12 @@ pub struct Chain {
genesis_hash: BlockHash,
/// Maximum number of nodes allowed to connect from this chain
max_nodes: usize,
/// Collator for the stats.
stats_collator: ChainStatsCollator,
/// Stats for this chain.
stats: ChainStats,
/// Timestamp of when the stats were last regenerated.
stats_last_regenerated: Instant,
}
pub enum AddNodeResult {
@@ -105,6 +115,9 @@ impl Chain {
timestamp: None,
genesis_hash,
max_nodes,
stats_collator: Default::default(),
stats: Default::default(),
stats_last_regenerated: Instant::now(),
}
}
@@ -119,7 +132,11 @@ impl Chain {
return AddNodeResult::Overquota;
}
let node_chain_label = &node.details().chain;
let details = node.details();
self.stats_collator
.add_or_remove_node(details, None, CounterValue::Increment);
let node_chain_label = &details.chain;
let label_result = self.labels.insert(node_chain_label);
let node_id = self.nodes.add(node);
@@ -140,6 +157,10 @@ impl Chain {
}
};
let details = node.details();
self.stats_collator
.add_or_remove_node(details, node.hwbench(), CounterValue::Decrement);
let node_chain_label = &node.details().chain;
let label_result = self.labels.remove(node_chain_label);
@@ -181,6 +202,19 @@ impl Chain {
}
return;
}
Payload::HwBench(ref hwbench) => {
let new_hwbench = common::node_types::NodeHwBench {
cpu_hashrate_score: hwbench.cpu_hashrate_score,
memory_memcpy_score: hwbench.memory_memcpy_score,
disk_sequential_write_score: hwbench.disk_sequential_write_score,
disk_random_write_score: hwbench.disk_random_write_score,
};
let old_hwbench = node.update_hwbench(new_hwbench);
self.stats_collator
.update_hwbench(old_hwbench.as_ref(), CounterValue::Decrement);
self.stats_collator
.update_hwbench(node.hwbench(), CounterValue::Increment);
}
_ => {}
}
@@ -210,6 +244,7 @@ impl Chain {
let nodes_len = self.nodes.len();
self.update_stale_nodes(now, feed);
self.regenerate_stats_if_necessary(feed);
let node = match self.nodes.get_mut(nid) {
Some(node) => node,
@@ -300,6 +335,21 @@ impl Chain {
}
}
fn regenerate_stats_if_necessary(&mut self, feed: &mut FeedMessageSerializer) {
let now = Instant::now();
let elapsed = now - self.stats_last_regenerated;
if elapsed < STATS_UPDATE_INTERVAL {
return;
}
self.stats_last_regenerated = now;
let new_stats = self.stats_collator.generate();
if new_stats != self.stats {
self.stats = new_stats;
feed.push(feed_message::ChainStatsUpdate(&self.stats));
}
}
pub fn update_node_location(
&mut self,
node_id: ChainNodeId,
@@ -340,4 +390,7 @@ impl Chain {
pub fn genesis_hash(&self) -> BlockHash {
self.genesis_hash
}
pub fn stats(&self) -> &ChainStats {
&self.stats
}
}
@@ -0,0 +1,225 @@
// 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/>.
use super::counter::{Counter, CounterValue};
use crate::feed_message::ChainStats;
// These are the benchmark scores generated on our reference hardware.
const REFERENCE_CPU_SCORE: u64 = 1028;
const REFERENCE_MEMORY_SCORE: u64 = 14899;
const REFERENCE_DISK_SEQUENTIAL_WRITE_SCORE: u64 = 485;
const REFERENCE_DISK_RANDOM_WRITE_SCORE: u64 = 222;
macro_rules! buckets {
(@try $value:expr, $bucket_min:expr, $bucket_max:expr,) => {
if $value < $bucket_max {
return ($bucket_min, Some($bucket_max));
}
};
($value:expr, $bucket_min:expr, $bucket_max:expr, $($remaining:expr,)*) => {
buckets! { @try $value, $bucket_min, $bucket_max, }
buckets! { $value, $bucket_max, $($remaining,)* }
};
($value:expr, $bucket_last:expr,) => {
($bucket_last, None)
}
}
/// Translates a given raw benchmark score into a relative measure
/// of how the score compares to the reference score.
///
/// The value returned is the range (in percent) within which the given score
/// falls into. For example, a value of `(90, Some(110))` means that the score
/// is between 90% and 110% of the reference score, with the lower bound being
/// inclusive and the upper bound being exclusive.
fn bucket_score(score: u64, reference_score: u64) -> (u32, Option<u32>) {
let relative_score = ((score as f64 / reference_score as f64) * 100.0) as u32;
buckets! {
relative_score,
0,
10,
30,
50,
70,
90,
110,
130,
150,
200,
300,
400,
500,
}
}
#[test]
fn test_bucket_score() {
assert_eq!(bucket_score(0, 100), (0, Some(10)));
assert_eq!(bucket_score(9, 100), (0, Some(10)));
assert_eq!(bucket_score(10, 100), (10, Some(30)));
assert_eq!(bucket_score(29, 100), (10, Some(30)));
assert_eq!(bucket_score(30, 100), (30, Some(50)));
assert_eq!(bucket_score(100, 100), (90, Some(110)));
assert_eq!(bucket_score(500, 100), (500, None));
}
fn bucket_memory(memory: u64) -> (u32, Option<u32>) {
let memory = memory / (1024 * 1024) / 1000;
buckets! {
memory,
1,
2,
4,
6,
8,
10,
16,
24,
32,
48,
56,
64,
}
}
#[derive(Default)]
pub struct ChainStatsCollator {
version: Counter<String>,
target_os: Counter<String>,
target_arch: Counter<String>,
cpu: Counter<String>,
memory: Counter<(u32, Option<u32>)>,
core_count: Counter<u32>,
linux_kernel: Counter<String>,
linux_distro: Counter<String>,
is_virtual_machine: Counter<bool>,
cpu_hashrate_score: Counter<(u32, Option<u32>)>,
memory_memcpy_score: Counter<(u32, Option<u32>)>,
disk_sequential_write_score: Counter<(u32, Option<u32>)>,
disk_random_write_score: Counter<(u32, Option<u32>)>,
}
impl ChainStatsCollator {
pub fn add_or_remove_node(
&mut self,
details: &common::node_types::NodeDetails,
hwbench: Option<&common::node_types::NodeHwBench>,
op: CounterValue,
) {
self.version.modify(Some(&*details.version), op);
self.target_os
.modify(details.target_os.as_ref().map(|value| &**value), op);
self.target_arch
.modify(details.target_arch.as_ref().map(|value| &**value), op);
let sysinfo = details.sysinfo.as_ref();
self.cpu.modify(
sysinfo
.and_then(|sysinfo| sysinfo.cpu.as_ref())
.map(|value| &**value),
op,
);
let memory = sysinfo.and_then(|sysinfo| sysinfo.memory.map(bucket_memory));
self.memory.modify(memory.as_ref(), op);
self.core_count
.modify(sysinfo.and_then(|sysinfo| sysinfo.core_count.as_ref()), op);
self.linux_kernel.modify(
sysinfo
.and_then(|sysinfo| sysinfo.linux_kernel.as_ref())
.map(|value| &**value),
op,
);
self.linux_distro.modify(
sysinfo
.and_then(|sysinfo| sysinfo.linux_distro.as_ref())
.map(|value| &**value),
op,
);
self.is_virtual_machine.modify(
sysinfo.and_then(|sysinfo| sysinfo.is_virtual_machine.as_ref()),
op,
);
self.update_hwbench(hwbench, op);
}
pub fn update_hwbench(
&mut self,
hwbench: Option<&common::node_types::NodeHwBench>,
op: CounterValue,
) {
self.cpu_hashrate_score.modify(
hwbench
.map(|hwbench| bucket_score(hwbench.cpu_hashrate_score, REFERENCE_CPU_SCORE))
.as_ref(),
op,
);
self.memory_memcpy_score.modify(
hwbench
.map(|hwbench| bucket_score(hwbench.memory_memcpy_score, REFERENCE_MEMORY_SCORE))
.as_ref(),
op,
);
self.disk_sequential_write_score.modify(
hwbench
.and_then(|hwbench| hwbench.disk_sequential_write_score)
.map(|score| bucket_score(score, REFERENCE_DISK_SEQUENTIAL_WRITE_SCORE))
.as_ref(),
op,
);
self.disk_random_write_score.modify(
hwbench
.and_then(|hwbench| hwbench.disk_random_write_score)
.map(|score| bucket_score(score, REFERENCE_DISK_RANDOM_WRITE_SCORE))
.as_ref(),
op,
);
}
pub fn generate(&self) -> ChainStats {
ChainStats {
version: self.version.generate_ranking_top(10),
target_os: self.target_os.generate_ranking_top(10),
target_arch: self.target_arch.generate_ranking_top(10),
cpu: self.cpu.generate_ranking_top(10),
memory: self.memory.generate_ranking_ordered(),
core_count: self.core_count.generate_ranking_top(10),
linux_kernel: self.linux_kernel.generate_ranking_top(10),
linux_distro: self.linux_distro.generate_ranking_top(10),
is_virtual_machine: self.is_virtual_machine.generate_ranking_ordered(),
cpu_hashrate_score: self.cpu_hashrate_score.generate_ranking_top(10),
memory_memcpy_score: self.memory_memcpy_score.generate_ranking_ordered(),
disk_sequential_write_score: self
.disk_sequential_write_score
.generate_ranking_ordered(),
disk_random_write_score: self.disk_random_write_score.generate_ranking_ordered(),
}
}
}
+119
View File
@@ -0,0 +1,119 @@
// 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/>.
use crate::feed_message::Ranking;
use std::collections::HashMap;
/// A data structure which counts how many occurences of a given key we've seen.
#[derive(Default)]
pub struct Counter<K> {
/// A map containing the number of occurences of a given key.
///
/// If there are none then the entry is removed.
map: HashMap<K, u64>,
/// The number of occurences where the key is `None`.
empty: u64,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum CounterValue {
Increment,
Decrement,
}
impl<K> Counter<K>
where
K: Sized + std::hash::Hash + Eq,
{
/// Either adds or removes a single occurence of a given `key`.
pub fn modify<'a, Q>(&mut self, key: Option<&'a Q>, op: CounterValue)
where
Q: ?Sized + std::hash::Hash + Eq,
K: std::borrow::Borrow<Q>,
Q: std::borrow::ToOwned<Owned = K>,
{
if let Some(key) = key {
if let Some(entry) = self.map.get_mut(key) {
match op {
CounterValue::Increment => {
*entry += 1;
}
CounterValue::Decrement => {
*entry -= 1;
if *entry == 0 {
// Don't keep entries for which there are no hits.
self.map.remove(key);
}
}
}
} else {
assert_eq!(op, CounterValue::Increment);
self.map.insert(key.to_owned(), 1);
}
} else {
match op {
CounterValue::Increment => {
self.empty += 1;
}
CounterValue::Decrement => {
self.empty -= 1;
}
}
}
}
/// Generates a top-N table of the most common keys.
pub fn generate_ranking_top(&self, max_count: usize) -> Ranking<K>
where
K: Clone,
{
let mut all: Vec<(&K, u64)> = self.map.iter().map(|(key, count)| (key, *count)).collect();
all.sort_unstable_by_key(|&(_, count)| !count);
let list = all
.iter()
.take(max_count)
.map(|&(key, count)| (key.clone(), count))
.collect();
let other = all
.iter()
.skip(max_count)
.fold(0, |sum, (_, count)| sum + *count);
Ranking {
list,
other,
unknown: self.empty,
}
}
/// Generates a sorted table of all of the keys.
pub fn generate_ranking_ordered(&self) -> Ranking<K>
where
K: Copy + Clone + Ord,
{
let mut list: Vec<(K, u64)> = self.map.iter().map(|(key, count)| (*key, *count)).collect();
list.sort_unstable_by_key(|&(key, count)| (key, !count));
Ranking {
list,
other: 0,
unknown: self.empty,
}
}
}
+2
View File
@@ -15,6 +15,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
mod chain;
mod chain_stats;
mod counter;
mod node;
mod state;
+13 -1
View File
@@ -17,7 +17,8 @@
use crate::find_location;
use common::node_message::SystemInterval;
use common::node_types::{
Block, BlockDetails, NodeDetails, NodeHardware, NodeIO, NodeLocation, NodeStats, Timestamp,
Block, BlockDetails, NodeDetails, NodeHardware, NodeHwBench, NodeIO, NodeLocation, NodeStats,
Timestamp,
};
use common::time;
@@ -47,6 +48,8 @@ pub struct Node {
stale: bool,
/// Unix timestamp for when node started up (falls back to connection time)
startup_time: Option<Timestamp>,
/// Hardware benchmark results for the node
hwbench: Option<NodeHwBench>,
}
impl Node {
@@ -67,6 +70,7 @@ impl Node {
location: None,
stale: false,
startup_time,
hwbench: None,
}
}
@@ -110,6 +114,14 @@ impl Node {
&self.best
}
pub fn hwbench(&self) -> Option<&NodeHwBench> {
self.hwbench.as_ref()
}
pub fn update_hwbench(&mut self, hwbench: NodeHwBench) -> Option<NodeHwBench> {
self.hwbench.replace(hwbench)
}
pub fn update_block(&mut self, block: Block) -> bool {
if block.height > self.best.block.height {
self.stale = false;
+8 -1
View File
@@ -15,7 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use super::node::Node;
use crate::feed_message::FeedMessageSerializer;
use crate::feed_message::{ChainStats, FeedMessageSerializer};
use crate::find_location;
use common::node_message::Payload;
use common::node_types::{Block, BlockHash, NodeDetails, Timestamp};
@@ -277,6 +277,9 @@ impl<'a> StateChain<'a> {
pub fn nodes_slice(&self) -> &[Option<Node>] {
self.chain.nodes_slice()
}
pub fn stats(&self) -> &ChainStats {
self.chain.stats()
}
}
#[cfg(test)]
@@ -289,10 +292,14 @@ mod test {
chain: chain.into(),
name: name.into(),
implementation: "Bar".into(),
target_arch: Some("x86_64".into()),
target_os: Some("linux".into()),
target_env: Some("env".into()),
version: "0.1".into(),
validator: None,
network_id: NetworkId::new(),
startup_time: None,
sysinfo: None,
}
}
@@ -76,6 +76,8 @@ pub enum Payload {
NotifyFinalized(Finalized),
#[serde(rename = "afg.authority_set")]
AfgAuthoritySet(AfgAuthoritySet),
#[serde(rename = "sysinfo.hwbench")]
HwBench(NodeHwBench),
}
impl From<Payload> for internal::Payload {
@@ -86,6 +88,7 @@ impl From<Payload> for internal::Payload {
Payload::BlockImport(m) => internal::Payload::BlockImport(m.into()),
Payload::NotifyFinalized(m) => internal::Payload::NotifyFinalized(m.into()),
Payload::AfgAuthoritySet(m) => internal::Payload::AfgAuthoritySet(m.into()),
Payload::HwBench(m) => internal::Payload::HwBench(m.into()),
}
}
}
@@ -183,6 +186,59 @@ impl From<Block> for node_types::Block {
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct NodeSysInfo {
pub cpu: Option<Box<str>>,
pub memory: Option<u64>,
pub core_count: Option<u32>,
pub linux_kernel: Option<Box<str>>,
pub linux_distro: Option<Box<str>>,
pub is_virtual_machine: Option<bool>,
}
impl From<NodeSysInfo> for node_types::NodeSysInfo {
fn from(sysinfo: NodeSysInfo) -> Self {
node_types::NodeSysInfo {
cpu: sysinfo.cpu,
memory: sysinfo.memory,
core_count: sysinfo.core_count,
linux_kernel: sysinfo.linux_kernel,
linux_distro: sysinfo.linux_distro,
is_virtual_machine: sysinfo.is_virtual_machine,
}
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct NodeHwBench {
pub cpu_hashrate_score: u64,
pub memory_memcpy_score: u64,
pub disk_sequential_write_score: Option<u64>,
pub disk_random_write_score: Option<u64>,
}
impl From<NodeHwBench> for node_types::NodeHwBench {
fn from(hwbench: NodeHwBench) -> Self {
node_types::NodeHwBench {
cpu_hashrate_score: hwbench.cpu_hashrate_score,
memory_memcpy_score: hwbench.memory_memcpy_score,
disk_sequential_write_score: hwbench.disk_sequential_write_score,
disk_random_write_score: hwbench.disk_random_write_score,
}
}
}
impl From<NodeHwBench> for internal::NodeHwBench {
fn from(msg: NodeHwBench) -> Self {
internal::NodeHwBench {
cpu_hashrate_score: msg.cpu_hashrate_score,
memory_memcpy_score: msg.memory_memcpy_score,
disk_sequential_write_score: msg.disk_sequential_write_score,
disk_random_write_score: msg.disk_random_write_score,
}
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct NodeDetails {
pub chain: Box<str>,
@@ -192,10 +248,30 @@ pub struct NodeDetails {
pub validator: Option<Box<str>>,
pub network_id: node_types::NetworkId,
pub startup_time: Option<Box<str>>,
pub target_os: Option<Box<str>>,
pub target_arch: Option<Box<str>>,
pub target_env: Option<Box<str>>,
pub sysinfo: Option<NodeSysInfo>,
}
impl From<NodeDetails> for node_types::NodeDetails {
fn from(details: NodeDetails) -> Self {
fn from(mut details: NodeDetails) -> Self {
// Migrate old-style `version` to the split metrics.
// TODO: Remove this once everyone updates their nodes.
if details.target_os.is_none()
&& details.target_arch.is_none()
&& details.target_env.is_none()
{
if let Some((version, target_arch, target_os, target_env)) =
split_old_style_version(&details.version)
{
details.target_arch = Some(target_arch.into());
details.target_os = Some(target_os.into());
details.target_env = Some(target_env.into());
details.version = version.into();
}
}
node_types::NodeDetails {
chain: details.chain,
name: details.name,
@@ -204,6 +280,10 @@ impl From<NodeDetails> for node_types::NodeDetails {
validator: details.validator,
network_id: details.network_id,
startup_time: details.startup_time,
target_os: details.target_os,
target_arch: details.target_arch,
target_env: details.target_env,
sysinfo: details.sysinfo.map(|sysinfo| sysinfo.into()),
}
}
}
@@ -211,6 +291,52 @@ impl From<NodeDetails> for node_types::NodeDetails {
type NodeMessageId = u64;
type BlockNumber = u64;
fn is_version_or_hash(name: &str) -> bool {
name.bytes().all(|byte| {
byte.is_ascii_digit()
|| byte == b'.'
|| byte == b'a'
|| byte == b'b'
|| byte == b'c'
|| byte == b'd'
|| byte == b'e'
|| byte == b'f'
})
}
/// Split an old style version string into its version + target_arch + target_os + target_arch parts.
fn split_old_style_version(version_and_target: &str) -> Option<(&str, &str, &str, &str)> {
// Old style versions are composed of the following parts:
// $version-$commit_hash-$arch-$os-$env
// where $commit_hash and $env are optional.
//
// For example these are all valid:
// 0.9.17-75dd6c7d0-x86_64-linux-gnu
// 0.9.17-75dd6c7d0-x86_64-linux
// 0.9.17-x86_64-linux-gnu
// 0.9.17-x86_64-linux
// 2.0.0-alpha.5-da487d19d-x86_64-linux
let mut iter = version_and_target.rsplit('-').take(3).skip(2);
// This will one of these: $arch, $commit_hash, $version
let item = iter.next()?;
let target_offset = if is_version_or_hash(item) {
item.as_ptr() as usize + item.len() + 1
} else {
item.as_ptr() as usize
} - version_and_target.as_ptr() as usize;
let version = version_and_target.get(0..target_offset - 1)?;
let mut target = version_and_target.get(target_offset..)?.split('-');
let target_arch = target.next()?;
let target_os = target.next()?;
let target_env = target.next().unwrap_or("");
Some((version, target_arch, target_os, target_env))
}
#[cfg(test)]
mod tests {
use super::*;
@@ -279,4 +405,46 @@ mod tests {
"message did not match the expected output",
);
}
#[test]
fn split_old_style_version_works() {
let (version, target_arch, target_os, target_env) =
split_old_style_version("0.9.17-75dd6c7d0-x86_64-linux-gnu").unwrap();
assert_eq!(version, "0.9.17-75dd6c7d0");
assert_eq!(target_arch, "x86_64");
assert_eq!(target_os, "linux");
assert_eq!(target_env, "gnu");
let (version, target_arch, target_os, target_env) =
split_old_style_version("0.9.17-75dd6c7d0-x86_64-linux").unwrap();
assert_eq!(version, "0.9.17-75dd6c7d0");
assert_eq!(target_arch, "x86_64");
assert_eq!(target_os, "linux");
assert_eq!(target_env, "");
let (version, target_arch, target_os, target_env) =
split_old_style_version("0.9.17-x86_64-linux-gnu").unwrap();
assert_eq!(version, "0.9.17");
assert_eq!(target_arch, "x86_64");
assert_eq!(target_os, "linux");
assert_eq!(target_env, "gnu");
let (version, target_arch, target_os, target_env) =
split_old_style_version("0.9.17-x86_64-linux").unwrap();
assert_eq!(version, "0.9.17");
assert_eq!(target_arch, "x86_64");
assert_eq!(target_os, "linux");
assert_eq!(target_env, "");
let (version, target_arch, target_os, target_env) =
split_old_style_version("2.0.0-alpha.5-da487d19d-x86_64-linux").unwrap();
assert_eq!(version, "2.0.0-alpha.5-da487d19d");
assert_eq!(target_arch, "x86_64");
assert_eq!(target_os, "linux");
assert_eq!(target_env, "");
assert_eq!(split_old_style_version(""), None);
assert_eq!(split_old_style_version("a"), None);
assert_eq!(split_old_style_version("a-b"), None);
}
}