2016-04-21 13:57:27 +02:00
|
|
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
|
|
|
// This file is part of Parity.
|
|
|
|
|
|
|
|
// Parity 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.
|
|
|
|
|
|
|
|
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2016-05-24 20:30:21 +02:00
|
|
|
extern crate ansi_term;
|
2016-07-17 23:00:57 +02:00
|
|
|
use self::ansi_term::Colour::{White, Yellow, Green, Cyan, Blue};
|
2016-05-25 09:57:31 +02:00
|
|
|
use self::ansi_term::Style;
|
2016-05-24 20:30:21 +02:00
|
|
|
|
2016-07-20 12:36:20 +02:00
|
|
|
use std::sync::{Arc};
|
2016-12-11 02:02:40 +01:00
|
|
|
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering};
|
2016-05-25 09:57:31 +02:00
|
|
|
use std::time::{Instant, Duration};
|
2016-12-11 02:02:40 +01:00
|
|
|
use io::{TimerToken, IoContext, IoHandler};
|
2016-07-15 10:11:14 +02:00
|
|
|
use isatty::{stdout_isatty};
|
2016-07-20 12:36:20 +02:00
|
|
|
use ethsync::{SyncProvider, ManageNetwork};
|
|
|
|
use util::{Uint, RwLock, Mutex, H256, Colour};
|
2016-04-21 13:57:27 +02:00
|
|
|
use ethcore::client::*;
|
2016-12-11 02:02:40 +01:00
|
|
|
use ethcore::service::ClientIoMessage;
|
2016-07-20 12:36:20 +02:00
|
|
|
use ethcore::views::BlockView;
|
2016-10-18 18:16:00 +02:00
|
|
|
use ethcore::snapshot::service::Service as SnapshotService;
|
|
|
|
use ethcore::snapshot::{RestorationStatus, SnapshotService as SS};
|
2016-04-21 13:57:27 +02:00
|
|
|
use number_prefix::{binary_prefix, Standalone, Prefixed};
|
2016-10-20 23:36:18 +02:00
|
|
|
use ethcore_rpc::is_major_importing;
|
2016-04-21 13:57:27 +02:00
|
|
|
|
|
|
|
pub struct Informant {
|
|
|
|
chain_info: RwLock<Option<BlockChainInfo>>,
|
|
|
|
cache_info: RwLock<Option<BlockChainCacheSize>>,
|
|
|
|
report: RwLock<Option<ClientReport>>,
|
2016-05-25 09:57:31 +02:00
|
|
|
last_tick: RwLock<Instant>,
|
|
|
|
with_color: bool,
|
2016-07-20 12:36:20 +02:00
|
|
|
client: Arc<Client>,
|
2016-10-18 18:16:00 +02:00
|
|
|
snapshot: Option<Arc<SnapshotService>>,
|
2016-07-20 12:36:20 +02:00
|
|
|
sync: Option<Arc<SyncProvider>>,
|
|
|
|
net: Option<Arc<ManageNetwork>>,
|
|
|
|
last_import: Mutex<Instant>,
|
|
|
|
skipped: AtomicUsize,
|
2016-10-26 13:57:54 +02:00
|
|
|
skipped_txs: AtomicUsize,
|
2016-12-11 02:02:40 +01:00
|
|
|
in_shutdown: AtomicBool,
|
2016-04-21 13:57:27 +02:00
|
|
|
}
|
|
|
|
|
2016-10-24 18:27:23 +02:00
|
|
|
/// Format byte counts to standard denominations.
|
|
|
|
pub fn format_bytes(b: usize) -> String {
|
|
|
|
match binary_prefix(b as f64) {
|
|
|
|
Standalone(bytes) => format!("{} bytes", bytes),
|
|
|
|
Prefixed(prefix, n) => format!("{:.0} {}B", n, prefix),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-16 12:18:27 +02:00
|
|
|
/// Something that can be converted to milliseconds.
|
2016-09-15 16:56:10 +02:00
|
|
|
pub trait MillisecondDuration {
|
2016-09-16 12:18:27 +02:00
|
|
|
/// Get the value in milliseconds.
|
2016-05-25 09:57:31 +02:00
|
|
|
fn as_milliseconds(&self) -> u64;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MillisecondDuration for Duration {
|
|
|
|
fn as_milliseconds(&self) -> u64 {
|
|
|
|
self.as_secs() * 1000 + self.subsec_nanos() as u64 / 1000000
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-21 13:57:27 +02:00
|
|
|
impl Informant {
|
2016-05-25 09:57:31 +02:00
|
|
|
/// Make a new instance potentially `with_color` output.
|
2016-10-18 18:16:00 +02:00
|
|
|
pub fn new(client: Arc<Client>, sync: Option<Arc<SyncProvider>>, net: Option<Arc<ManageNetwork>>, snapshot: Option<Arc<SnapshotService>>, with_color: bool) -> Self {
|
2016-05-25 09:57:31 +02:00
|
|
|
Informant {
|
|
|
|
chain_info: RwLock::new(None),
|
|
|
|
cache_info: RwLock::new(None),
|
|
|
|
report: RwLock::new(None),
|
|
|
|
last_tick: RwLock::new(Instant::now()),
|
|
|
|
with_color: with_color,
|
2016-07-20 12:36:20 +02:00
|
|
|
client: client,
|
2016-10-18 18:16:00 +02:00
|
|
|
snapshot: snapshot,
|
2016-07-20 12:36:20 +02:00
|
|
|
sync: sync,
|
|
|
|
net: net,
|
|
|
|
last_import: Mutex::new(Instant::now()),
|
|
|
|
skipped: AtomicUsize::new(0),
|
2016-10-26 13:57:54 +02:00
|
|
|
skipped_txs: AtomicUsize::new(0),
|
2016-12-11 02:02:40 +01:00
|
|
|
in_shutdown: AtomicBool::new(false),
|
2016-05-25 09:57:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-11 02:02:40 +01:00
|
|
|
/// Signal that we're shutting down; no more output necessary.
|
|
|
|
pub fn shutdown(&self) {
|
|
|
|
self.in_shutdown.store(true, ::std::sync::atomic::Ordering::SeqCst);
|
|
|
|
}
|
2016-07-13 19:59:59 +02:00
|
|
|
|
2016-07-11 17:02:42 +02:00
|
|
|
#[cfg_attr(feature="dev", allow(match_bool))]
|
2016-07-20 12:36:20 +02:00
|
|
|
pub fn tick(&self) {
|
2016-07-13 19:59:59 +02:00
|
|
|
let elapsed = self.last_tick.read().elapsed();
|
2016-05-25 09:57:31 +02:00
|
|
|
if elapsed < Duration::from_secs(5) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-07-20 12:36:20 +02:00
|
|
|
let chain_info = self.client.chain_info();
|
|
|
|
let queue_info = self.client.queue_info();
|
|
|
|
let cache_info = self.client.blockchain_cache_info();
|
|
|
|
let network_config = self.net.as_ref().map(|n| n.network_config());
|
|
|
|
let sync_status = self.sync.as_ref().map(|s| s.status());
|
2016-04-21 13:57:27 +02:00
|
|
|
|
2016-10-20 23:36:18 +02:00
|
|
|
let importing = is_major_importing(sync_status.map(|s| s.state), self.client.queue_info());
|
2016-10-18 18:16:00 +02:00
|
|
|
let (snapshot_sync, snapshot_current, snapshot_total) = self.snapshot.as_ref().map_or((false, 0, 0), |s|
|
|
|
|
match s.status() {
|
|
|
|
RestorationStatus::Ongoing { state_chunks, block_chunks, state_chunks_done, block_chunks_done } =>
|
|
|
|
(true, state_chunks_done + block_chunks_done, state_chunks + block_chunks),
|
|
|
|
_ => (false, 0, 0),
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if !importing && !snapshot_sync && elapsed < Duration::from_secs(30) {
|
2016-07-17 23:00:57 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
*self.last_tick.write() = Instant::now();
|
|
|
|
|
2016-07-13 19:59:59 +02:00
|
|
|
let mut write_report = self.report.write();
|
2016-07-20 12:36:20 +02:00
|
|
|
let report = self.client.report();
|
2016-04-21 13:57:27 +02:00
|
|
|
|
2016-07-15 10:11:14 +02:00
|
|
|
let paint = |c: Style, t: String| match self.with_color && stdout_isatty() {
|
2016-05-25 09:57:31 +02:00
|
|
|
true => format!("{}", c.paint(t)),
|
|
|
|
false => t,
|
|
|
|
};
|
|
|
|
|
2016-07-23 17:27:23 +02:00
|
|
|
info!(target: "import", "{} {} {}",
|
2016-07-17 23:00:57 +02:00
|
|
|
match importing {
|
2016-10-18 18:16:00 +02:00
|
|
|
true => match snapshot_sync {
|
|
|
|
false => format!("Syncing {} {} {} {}+{} Qed",
|
|
|
|
paint(White.bold(), format!("{:>8}", format!("#{}", chain_info.best_block_number))),
|
|
|
|
paint(White.bold(), format!("{}", chain_info.best_block_hash)),
|
|
|
|
{
|
|
|
|
let last_report = match *write_report { Some(ref last_report) => last_report.clone(), _ => ClientReport::default() };
|
|
|
|
format!("{} blk/s {} tx/s {} Mgas/s",
|
|
|
|
paint(Yellow.bold(), format!("{:4}", ((report.blocks_imported - last_report.blocks_imported) * 1000) as u64 / elapsed.as_milliseconds())),
|
|
|
|
paint(Yellow.bold(), format!("{:4}", ((report.transactions_applied - last_report.transactions_applied) * 1000) as u64 / elapsed.as_milliseconds())),
|
|
|
|
paint(Yellow.bold(), format!("{:3}", ((report.gas_processed - last_report.gas_processed) / From::from(elapsed.as_milliseconds() * 1000)).low_u64()))
|
|
|
|
)
|
|
|
|
},
|
|
|
|
paint(Green.bold(), format!("{:5}", queue_info.unverified_queue_size)),
|
|
|
|
paint(Green.bold(), format!("{:5}", queue_info.verified_queue_size))
|
|
|
|
),
|
|
|
|
true => format!("Syncing snapshot {}/{}", snapshot_current, snapshot_total),
|
|
|
|
},
|
2016-07-17 23:00:57 +02:00
|
|
|
false => String::new(),
|
|
|
|
},
|
2016-07-20 12:36:20 +02:00
|
|
|
match (&sync_status, &network_config) {
|
|
|
|
(&Some(ref sync_info), &Some(ref net_config)) => format!("{}{}/{}/{} peers",
|
2016-07-17 23:00:57 +02:00
|
|
|
match importing {
|
|
|
|
true => format!("{} ", paint(Green.bold(), format!("{:>8}", format!("#{}", sync_info.last_imported_block_number.unwrap_or(chain_info.best_block_number))))),
|
2016-10-18 18:16:00 +02:00
|
|
|
false => match sync_info.last_imported_old_block_number {
|
|
|
|
Some(number) => format!("{} ", paint(Yellow.bold(), format!("{:>8}", format!("#{}", number)))),
|
|
|
|
None => String::new(),
|
|
|
|
}
|
2016-07-17 23:00:57 +02:00
|
|
|
},
|
|
|
|
paint(Cyan.bold(), format!("{:2}", sync_info.num_active_peers)),
|
|
|
|
paint(Cyan.bold(), format!("{:2}", sync_info.num_peers)),
|
2016-08-11 17:19:20 +02:00
|
|
|
paint(Cyan.bold(), format!("{:2}", sync_info.current_max_peers(net_config.min_peers, net_config.max_peers))),
|
2016-07-17 23:00:57 +02:00
|
|
|
),
|
2016-07-20 12:36:20 +02:00
|
|
|
_ => String::new(),
|
2016-07-17 23:00:57 +02:00
|
|
|
},
|
2016-07-20 12:36:20 +02:00
|
|
|
format!("{} db {} chain {} queue{}",
|
2016-10-24 18:27:23 +02:00
|
|
|
paint(Blue.bold(), format!("{:>8}", format_bytes(report.state_db_mem))),
|
|
|
|
paint(Blue.bold(), format!("{:>8}", format_bytes(cache_info.total()))),
|
|
|
|
paint(Blue.bold(), format!("{:>8}", format_bytes(queue_info.mem_used))),
|
2016-07-20 12:36:20 +02:00
|
|
|
match sync_status {
|
2016-10-24 18:27:23 +02:00
|
|
|
Some(ref sync_info) => format!(" {} sync", paint(Blue.bold(), format!("{:>8}", format_bytes(sync_info.mem_used)))),
|
2016-07-17 23:00:57 +02:00
|
|
|
_ => String::new(),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
2016-04-21 13:57:27 +02:00
|
|
|
|
2016-08-10 16:29:40 +02:00
|
|
|
*self.chain_info.write() = Some(chain_info);
|
|
|
|
*self.cache_info.write() = Some(cache_info);
|
|
|
|
*write_report = Some(report);
|
2016-04-21 13:57:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-20 12:36:20 +02:00
|
|
|
impl ChainNotify for Informant {
|
2016-07-26 00:20:37 +02:00
|
|
|
fn new_blocks(&self, imported: Vec<H256>, _invalid: Vec<H256>, _enacted: Vec<H256>, _retracted: Vec<H256>, _sealed: Vec<H256>, duration: u64) {
|
2016-07-20 12:36:20 +02:00
|
|
|
let mut last_import = self.last_import.lock();
|
2016-10-20 23:36:18 +02:00
|
|
|
let sync_state = self.sync.as_ref().map(|s| s.status().state);
|
|
|
|
let importing = is_major_importing(sync_state, self.client.queue_info());
|
2016-10-26 13:57:54 +02:00
|
|
|
|
|
|
|
let ripe = Instant::now() > *last_import + Duration::from_secs(1) && !importing;
|
|
|
|
let txs_imported = imported.iter()
|
|
|
|
.take(imported.len() - if ripe {1} else {0})
|
2016-11-22 10:24:22 +01:00
|
|
|
.filter_map(|h| self.client.block(BlockId::Hash(*h)))
|
2016-10-26 13:57:54 +02:00
|
|
|
.map(|b| BlockView::new(&b).transactions_count())
|
|
|
|
.sum();
|
|
|
|
|
|
|
|
if ripe {
|
2016-11-22 10:24:22 +01:00
|
|
|
if let Some(block) = imported.last().and_then(|h| self.client.block(BlockId::Hash(*h))) {
|
2016-07-24 17:38:29 +02:00
|
|
|
let view = BlockView::new(&block);
|
|
|
|
let header = view.header();
|
|
|
|
let tx_count = view.transactions_count();
|
|
|
|
let size = block.len();
|
2016-11-01 22:44:24 +01:00
|
|
|
let (skipped, skipped_txs) = (self.skipped.load(AtomicOrdering::Relaxed) + imported.len() - 1, self.skipped_txs.load(AtomicOrdering::Relaxed) + txs_imported);
|
2016-07-24 17:38:29 +02:00
|
|
|
info!(target: "import", "Imported {} {} ({} txs, {} Mgas, {} ms, {} KiB){}",
|
2016-07-20 12:36:20 +02:00
|
|
|
Colour::White.bold().paint(format!("#{}", header.number())),
|
|
|
|
Colour::White.bold().paint(format!("{}", header.hash())),
|
|
|
|
Colour::Yellow.bold().paint(format!("{}", tx_count)),
|
2016-08-29 11:35:24 +02:00
|
|
|
Colour::Yellow.bold().paint(format!("{:.2}", header.gas_used().low_u64() as f32 / 1000000f32)),
|
2016-07-20 12:36:20 +02:00
|
|
|
Colour::Purple.bold().paint(format!("{:.2}", duration as f32 / 1000000f32)),
|
|
|
|
Colour::Blue.bold().paint(format!("{:.2}", size as f32 / 1024f32)),
|
2016-10-26 13:57:54 +02:00
|
|
|
if skipped > 0 {
|
|
|
|
format!(" + another {} block(s) containing {} tx(s)",
|
|
|
|
Colour::Red.bold().paint(format!("{}", skipped)),
|
|
|
|
Colour::Red.bold().paint(format!("{}", skipped_txs))
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
String::new()
|
|
|
|
}
|
2016-07-24 17:38:29 +02:00
|
|
|
);
|
2016-10-26 13:57:54 +02:00
|
|
|
self.skipped.store(0, AtomicOrdering::Relaxed);
|
|
|
|
self.skipped_txs.store(0, AtomicOrdering::Relaxed);
|
2016-07-24 17:38:29 +02:00
|
|
|
*last_import = Instant::now();
|
2016-07-20 12:36:20 +02:00
|
|
|
}
|
|
|
|
} else {
|
2016-07-26 00:20:37 +02:00
|
|
|
self.skipped.fetch_add(imported.len(), AtomicOrdering::Relaxed);
|
2016-10-26 13:57:54 +02:00
|
|
|
self.skipped_txs.fetch_add(txs_imported, AtomicOrdering::Relaxed);
|
2016-07-20 12:36:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-11 02:02:40 +01:00
|
|
|
const INFO_TIMER: TimerToken = 0;
|
|
|
|
|
2016-12-10 23:58:39 +01:00
|
|
|
impl IoHandler<ClientIoMessage> for Informant {
|
|
|
|
fn initialize(&self, io: &IoContext<ClientIoMessage>) {
|
|
|
|
io.register_timer(INFO_TIMER, 5000).expect("Error registering timer");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn timeout(&self, _io: &IoContext<ClientIoMessage>, timer: TimerToken) {
|
2016-12-11 02:02:40 +01:00
|
|
|
if timer == INFO_TIMER && !self.in_shutdown.load(AtomicOrdering::SeqCst) {
|
|
|
|
self.tick();
|
2016-12-10 23:58:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|