diff --git a/Cargo.toml b/Cargo.toml index 11f7fb06a..96692476b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ serde_json = "~1.0.7" slog = { version = "^2.0.12", features = ["max_level_trace", "release_max_level_trace"] } term = "~0.4.6" time = "^0.1" +chrono = "^0.4.0" cursive = { git = "https://github.com/yeastplume/Cursive" } # TODO - once "patch" is available we should be able to clean up the workspace dependencies # [patch.crate-io] diff --git a/core/src/global.rs b/core/src/global.rs index 766d9d598..b50554849 100644 --- a/core/src/global.rs +++ b/core/src/global.rs @@ -29,6 +29,7 @@ use consensus::{BLOCK_TIME_SEC, CUT_THROUGH_HORIZON, DIFFICULTY_ADJUST_WINDOW, I MEDIAN_TIME_WINDOW}; use core::target::Difficulty; use consensus::TargetError; +use util::LOGGER; /// Define these here, as they should be developer-set, not really tweakable /// by users diff --git a/grin/src/lib.rs b/grin/src/lib.rs index 522ed0da1..2fed9c816 100644 --- a/grin/src/lib.rs +++ b/grin/src/lib.rs @@ -48,7 +48,9 @@ mod server; mod seed; mod sync; pub mod types; +pub mod stats; mod miner; pub use server::Server; -pub use types::{Seeding, ServerConfig, ServerStats}; +pub use types::{Seeding, ServerConfig}; +pub use stats::ServerStats; diff --git a/grin/src/miner.rs b/grin/src/miner.rs index 983ea9228..94d2b1665 100644 --- a/grin/src/miner.rs +++ b/grin/src/miner.rs @@ -35,7 +35,8 @@ use core::ser; use core::global; use core::ser::AsFixedBytes; use util::LOGGER; -use types::{Error, MiningStats}; +use types::Error; +use stats::MiningStats; use chain; use pool; diff --git a/grin/src/server.rs b/grin/src/server.rs index 570fd9b08..bcc676a18 100644 --- a/grin/src/server.rs +++ b/grin/src/server.rs @@ -25,13 +25,15 @@ use std::time; use adapters::*; use api; use chain; -use core::{genesis, global}; +use core::{consensus, genesis, global}; +use core::core::target::Difficulty; use miner; use p2p; use pool; use seed; use sync; use types::*; +use stats::*; use pow; use util::LOGGER; @@ -263,6 +265,58 @@ impl Server { pub fn get_server_stats(&self) -> Result { let mining_stats = self.state_info.mining_stats.read().unwrap().clone(); let awaiting_peers = self.state_info.awaiting_peers.load(Ordering::Relaxed); + + // Fill out stats on our current difficulty calculation + // TODO: check the overhead of calculating this again isn't too much + // could return it from next_difficulty, but would rather keep consensus + // code clean. This may be handy for testing but not really needed + // for release + let diff_stats = { + let diff_iter = self.chain.difficulty_iter(); + let last_blocks: Vec> = + global::difficulty_data_to_vector(diff_iter) + .into_iter() + .skip(consensus::MEDIAN_TIME_WINDOW as usize) + .take(consensus::DIFFICULTY_ADJUST_WINDOW as usize) + .collect(); + + let mut last_time = last_blocks[0].clone().unwrap().0; + let tip_height = self.chain.head().unwrap().height as i64; + let earliest_block_height = tip_height as i64 - last_blocks.len() as i64; + + let mut i = 1; + + let diff_entries: Vec = last_blocks + .iter() + .skip(1) + .map(|n| { + let (time, diff) = n.clone().unwrap(); + let dur = time - last_time; + let height = earliest_block_height + i + 1; + let index = tip_height - height; + i += 1; + last_time = time; + DiffBlock { + block_number: height, + block_index: index, + difficulty: diff.into_num(), + time: time, + duration: dur, + } + }) + .collect(); + + let block_time_sum = diff_entries.iter().fold(0, |sum, t| sum + t.duration); + let block_diff_sum = diff_entries.iter().fold(0, |sum, d| sum + d.difficulty); + DiffStats { + height: tip_height as u64, + last_blocks: diff_entries, + average_block_time: block_time_sum / consensus::DIFFICULTY_ADJUST_WINDOW, + average_difficulty: block_diff_sum / consensus::DIFFICULTY_ADJUST_WINDOW, + window_size: consensus::DIFFICULTY_ADJUST_WINDOW, + } + }; + let peer_stats = self.p2p .peers .connected_peers() @@ -280,6 +334,7 @@ impl Server { awaiting_peers: awaiting_peers, mining_stats: mining_stats, peer_stats: peer_stats, + diff_stats: diff_stats, }) } diff --git a/grin/src/stats.rs b/grin/src/stats.rs new file mode 100644 index 000000000..4ff5b7dd3 --- /dev/null +++ b/grin/src/stats.rs @@ -0,0 +1,168 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Server stat collection types, to be used by tests, logging or GUI/TUI +//! to collect information about server status + +use std::sync::{Arc, RwLock}; +use std::sync::atomic::AtomicBool; + +use chain; +use p2p; +use pow; + +/// Server state info collection struct, to be passed around into internals +/// and populated when required +#[derive(Clone)] +pub struct ServerStateInfo { + /// whether we're in a state of waiting for peers at startup + pub awaiting_peers: Arc, + /// Mining stats + pub mining_stats: Arc>, +} + +impl Default for ServerStateInfo { + fn default() -> ServerStateInfo { + ServerStateInfo { + awaiting_peers: Arc::new(AtomicBool::new(false)), + mining_stats: Arc::new(RwLock::new(MiningStats::default())), + } + } +} +/// Simpler thread-unware version of above to be populated and retured to +/// consumers might be interested in, such as test results or UI +#[derive(Clone)] +pub struct ServerStats { + /// Number of peers + pub peer_count: u32, + /// Chain head + pub head: chain::Tip, + /// sync header head + pub header_head: chain::Tip, + /// Whether we're currently syncing + pub is_syncing: bool, + /// Whether we're awaiting peers + pub awaiting_peers: bool, + /// Handle to current mining stats + pub mining_stats: MiningStats, + /// Peer stats + pub peer_stats: Vec, + /// Difficulty calculation statistics + pub diff_stats: DiffStats, +} + +/// Struct to return relevant information about the mining process +/// back to interested callers (such as the TUI) +#[derive(Clone)] +pub struct MiningStats { + /// whether mining is enabled + pub is_enabled: bool, + /// whether we're currently mining + pub is_mining: bool, + /// combined graphs per second + pub combined_gps: f64, + /// what block height we're mining at + pub block_height: u64, + /// current network difficulty we're working on + pub network_difficulty: u64, + /// cuckoo size used for mining + pub cuckoo_size: u16, + /// Individual device status from Cuckoo-Miner + pub device_stats: Option>>, +} + +/// Stats on the last WINDOW blocks and the difficulty calculation +#[derive(Clone)] +pub struct DiffStats { + /// latest height + pub height: u64, + /// Last WINDOW block data + pub last_blocks: Vec, + /// Average block time for last WINDOW blocks + pub average_block_time: u64, + /// Average WINDOW difficulty + pub average_difficulty: u64, + /// WINDOW size + pub window_size: u64, +} + +/// Last n blocks for difficulty calculation purposes +#[derive(Clone, Debug)] +pub struct DiffBlock { + /// Block number (can be negative for a new chain) + pub block_number: i64, + /// Ordinal index from current block + pub block_index: i64, + /// Block network difficulty + pub difficulty: u64, + /// Time block was found (epoch seconds) + pub time: u64, + /// Duration since previous block (epoch seconds) + pub duration: u64, +} + +/// Struct to return relevant information about peers +#[derive(Clone, Debug)] +pub struct PeerStats { + /// Current state of peer + pub state: String, + /// Address + pub addr: String, + /// version running + pub version: u32, + /// version running + pub total_difficulty: u64, + /// direction + pub direction: String, +} + +impl PeerStats { + /// Convert from a peer directly + pub fn from_peer(peer: &p2p::Peer) -> PeerStats { + // State + let mut state = "Disconnected"; + if peer.is_connected() { + state = "Connected"; + } + if peer.is_banned() { + state = "Banned"; + } + let addr = peer.info.addr.to_string(); + let direction = match peer.info.direction { + p2p::types::Direction::Inbound => "Inbound", + p2p::types::Direction::Outbound => "Outbound", + }; + PeerStats { + state: state.to_string(), + addr: addr, + version: peer.info.version, + total_difficulty: peer.info.total_difficulty.into_num(), + direction: direction.to_string(), + } + } +} + +impl Default for MiningStats { + fn default() -> MiningStats { + MiningStats { + is_enabled: false, + is_mining: false, + combined_gps: 0.0, + block_height: 0, + network_difficulty: 0, + cuckoo_size: 0, + device_stats: None, + } + } +} diff --git a/grin/src/types.rs b/grin/src/types.rs index 1cda706f0..7e77c52ea 100644 --- a/grin/src/types.rs +++ b/grin/src/types.rs @@ -15,8 +15,6 @@ //! Server types use std::convert::From; -use std::sync::{Arc, RwLock}; -use std::sync::atomic::AtomicBool; use api; use chain; @@ -178,116 +176,3 @@ impl Default for ServerConfig { } } } - -/// Server state info collection struct, to be passed around into internals -/// and populated when required -#[derive(Clone)] -pub struct ServerStateInfo { - /// whether we're in a state of waiting for peers at startup - pub awaiting_peers: Arc, - /// Mining stats - pub mining_stats: Arc>, -} - -impl Default for ServerStateInfo { - fn default() -> ServerStateInfo { - ServerStateInfo { - awaiting_peers: Arc::new(AtomicBool::new(false)), - mining_stats: Arc::new(RwLock::new(MiningStats::default())), - } - } -} -/// Simpler thread-unware version of above to be populated and retured to -/// consumers might be interested in, such as test results or UI -#[derive(Clone)] -pub struct ServerStats { - /// Number of peers - pub peer_count: u32, - /// Chain head - pub head: chain::Tip, - /// sync header head - pub header_head: chain::Tip, - /// Whether we're currently syncing - pub is_syncing: bool, - /// Whether we're awaiting peers - pub awaiting_peers: bool, - /// Handle to current mining stats - pub mining_stats: MiningStats, - /// Peer stats - pub peer_stats: Vec, -} - -/// Struct to return relevant information about the mining process -/// back to interested callers (such as the TUI) -#[derive(Clone)] -pub struct MiningStats { - /// whether mining is enabled - pub is_enabled: bool, - /// whether we're currently mining - pub is_mining: bool, - /// combined graphs per second - pub combined_gps: f64, - /// what block height we're mining at - pub block_height: u64, - /// current network difficulty we're working on - pub network_difficulty: u64, - /// cuckoo size used for mining - pub cuckoo_size: u16, - /// Individual device status from Cuckoo-Miner - pub device_stats: Option>>, -} - -/// Struct to return relevant information about peers -#[derive(Clone, Debug)] -pub struct PeerStats { - /// Current state of peer - pub state: String, - /// Address - pub addr: String, - /// version running - pub version: u32, - /// version running - pub total_difficulty: u64, - /// direction - pub direction: String, -} - -impl PeerStats { - /// Convert from a peer directly - pub fn from_peer(peer: &p2p::Peer) -> PeerStats { - // State - let mut state = "Disconnected"; - if peer.is_connected() { - state = "Connected"; - } - if peer.is_banned() { - state = "Banned"; - } - let addr = peer.info.addr.to_string(); - let direction = match peer.info.direction { - p2p::types::Direction::Inbound => "Inbound", - p2p::types::Direction::Outbound => "Outbound", - }; - PeerStats { - state: state.to_string(), - addr: addr, - version: peer.info.version, - total_difficulty: peer.info.total_difficulty.into_num(), - direction: direction.to_string(), - } - } -} - -impl Default for MiningStats { - fn default() -> MiningStats { - MiningStats { - is_enabled: false, - is_mining: false, - combined_gps: 0.0, - block_height: 0, - network_difficulty: 0, - cuckoo_size: 0, - device_stats: None, - } - } -} diff --git a/pow/src/lib.rs b/pow/src/lib.rs index 8431355d0..4cfae3ab5 100644 --- a/pow/src/lib.rs +++ b/pow/src/lib.rs @@ -86,6 +86,10 @@ pub fn mine_genesis_block( miner_config: Option, ) -> Result { let mut gen = genesis::genesis_testnet2(); + if global::is_user_testing_mode() { + gen = genesis::genesis_dev(); + gen.header.timestamp = time::now(); + } // total_difficulty on the genesis header *is* the difficulty of that block let genesis_difficulty = gen.header.total_difficulty.clone(); diff --git a/src/bin/grin.rs b/src/bin/grin.rs index c675f5cc0..b123e291b 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -110,6 +110,7 @@ fn main() { let run_tui = global_config.members.as_mut().unwrap().server.run_tui; if run_tui.is_some() && run_tui.unwrap() { log_conf.log_to_stdout = false; + log_conf.tui_running = Some(true); } init_logger(Some(log_conf)); global::set_mining_mode( diff --git a/src/bin/tui/constants.rs b/src/bin/tui/constants.rs index 6f904ac86..124f45d9e 100644 --- a/src/bin/tui/constants.rs +++ b/src/bin/tui/constants.rs @@ -24,9 +24,12 @@ pub const TABLE_PEER_STATUS: &str = "peer_status_table"; // Mining View pub const VIEW_MINING: &str = "mining_view"; +pub const SUBMENU_MINING_BUTTON: &str = "mining_submenu_button"; pub const TABLE_MINING_STATUS: &str = "mining_status_table"; +pub const TABLE_MINING_DIFF_STATUS: &str = "mining_diff_status_table"; // Menu and root elements +pub const MAIN_MENU: &str = "main_menu"; pub const ROOT_STACK: &str = "root_stack"; // Logo (not final, to be used somewhere eventually diff --git a/src/bin/tui/menu.rs b/src/bin/tui/menu.rs index 1549dfaa2..8c4bb3ca4 100644 --- a/src/bin/tui/menu.rs +++ b/src/bin/tui/menu.rs @@ -18,18 +18,23 @@ use cursive::Cursive; use cursive::view::View; use cursive::align::HAlign; use cursive::event::{EventResult, Key}; -use cursive::views::{BoxView, LinearLayout, OnEventView, SelectView, StackView, TextView}; +use cursive::view::Identifiable; +use cursive::views::{BoxView, LinearLayout, OnEventView, SelectView, StackView, TextView, ViewRef}; use cursive::direction::Orientation; use tui::constants::*; pub fn create() -> Box { - let mut main_menu = SelectView::new().h_align(HAlign::Left); - main_menu.add_item("Basic Status", VIEW_BASIC_STATUS); - main_menu.add_item("Peers and Sync", VIEW_PEER_SYNC); - main_menu.add_item("Mining", VIEW_MINING); - let change_view = |s: &mut Cursive, v: &str| { - if v == "" { + let mut main_menu = SelectView::new().h_align(HAlign::Left).with_id(MAIN_MENU); + main_menu + .get_mut() + .add_item("Basic Status", VIEW_BASIC_STATUS); + main_menu + .get_mut() + .add_item("Peers and Sync", VIEW_PEER_SYNC); + main_menu.get_mut().add_item("Mining", VIEW_MINING); + let change_view = |s: &mut Cursive, v: &&str| { + if *v == "" { return; } @@ -39,24 +44,33 @@ pub fn create() -> Box { }); }; - main_menu.set_on_submit(change_view); - - let main_menu = OnEventView::new(main_menu) - .on_pre_event_inner('k', |s| { - s.select_up(1); - Some(EventResult::Consumed(None)) - }) - .on_pre_event_inner('j', |s| { - s.select_down(1); - Some(EventResult::Consumed(None)) - }) - .on_pre_event_inner(Key::Tab, |s| { - if s.selected_id().unwrap() == s.len() - 1 { - s.set_selection(0); - } else { - s.select_down(1); + main_menu.get_mut().set_on_select(change_view); + main_menu + .get_mut() + .set_on_submit(|c: &mut Cursive, v: &str| { + if v == VIEW_MINING { + let _ = c.focus_id(SUBMENU_MINING_BUTTON); } - Some(EventResult::Consumed(None)) + }); + let main_menu = OnEventView::new(main_menu) + .on_pre_event('j', move |c| { + let mut s: ViewRef> = c.find_id(MAIN_MENU).unwrap(); + s.select_down(1)(c); + Some(EventResult::Consumed(None)); + }) + .on_pre_event('k', move |c| { + let mut s: ViewRef> = c.find_id(MAIN_MENU).unwrap(); + s.select_up(1)(c); + Some(EventResult::Consumed(None)); + }) + .on_pre_event(Key::Tab, move |c| { + let mut s: ViewRef> = c.find_id(MAIN_MENU).unwrap(); + if s.selected_id().unwrap() == s.len() - 1 { + s.set_selection(0)(c); + } else { + s.select_down(1)(c); + } + Some(EventResult::Consumed(None)); }); let main_menu = LinearLayout::new(Orientation::Vertical) .child(BoxView::with_full_height(main_menu)) diff --git a/src/bin/tui/mining.rs b/src/bin/tui/mining.rs index 1cfcf2bf0..94fe149df 100644 --- a/src/bin/tui/mining.rs +++ b/src/bin/tui/mining.rs @@ -17,15 +17,19 @@ use std::cmp::Ordering; use cursive::Cursive; +use cursive::event::Key; use cursive::view::View; -use cursive::views::{BoxView, Dialog, LinearLayout, TextView}; +use cursive::views::{BoxView, Button, Dialog, LinearLayout, OnEventView, Panel, StackView, + TextView}; use cursive::direction::Orientation; use cursive::traits::*; +use std::time; +use tui::chrono::prelude::*; use tui::constants::*; use tui::types::*; -use grin::types::ServerStats; +use grin::stats::*; use tui::pow::cuckoo_miner::CuckooMinerDeviceStats; use tui::table::{TableView, TableViewItem}; @@ -100,12 +104,80 @@ impl TableViewItem for CuckooMinerDeviceStats { } } +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +enum DiffColumn { + BlockNumber, + Index, + Difficulty, + Time, + Duration, +} + +impl DiffColumn { + fn _as_str(&self) -> &str { + match *self { + DiffColumn::BlockNumber => "Block Number", + DiffColumn::Index => "Block Index", + DiffColumn::Difficulty => "Network Difficulty", + DiffColumn::Time => "Block Time", + DiffColumn::Duration => "Duration", + } + } +} + +impl TableViewItem for DiffBlock { + fn to_column(&self, column: DiffColumn) -> String { + let naive_datetime = NaiveDateTime::from_timestamp(self.time as i64, 0); + let datetime: DateTime = DateTime::from_utc(naive_datetime, Utc); + + match column { + DiffColumn::BlockNumber => self.block_number.to_string(), + DiffColumn::Index => self.block_index.to_string(), + DiffColumn::Difficulty => self.difficulty.to_string(), + DiffColumn::Time => format!("{}", datetime).to_string(), + DiffColumn::Duration => format!("{}s", self.duration).to_string(), + } + } + + fn cmp(&self, _other: &Self, column: DiffColumn) -> Ordering + where + Self: Sized, + { + match column { + DiffColumn::BlockNumber => Ordering::Equal, + DiffColumn::Index => Ordering::Equal, + DiffColumn::Difficulty => Ordering::Equal, + DiffColumn::Time => Ordering::Equal, + DiffColumn::Duration => Ordering::Equal, + } + } +} /// Mining status view pub struct TUIMiningView; impl TUIStatusListener for TUIMiningView { /// Create the mining view fn create() -> Box { + let devices_button = Button::new_raw("Status / Devices", |s| { + let _ = s.call_on_id("mining_stack_view", |sv: &mut StackView| { + let pos = sv.find_layer_from_id("mining_device_view").unwrap(); + sv.move_to_front(pos); + }); + }).with_id(SUBMENU_MINING_BUTTON); + let difficulty_button = Button::new_raw("Difficulty", |s| { + let _ = s.call_on_id("mining_stack_view", |sv: &mut StackView| { + let pos = sv.find_layer_from_id("mining_difficulty_view").unwrap(); + sv.move_to_front(pos); + }); + }); + let mining_submenu = LinearLayout::new(Orientation::Horizontal) + .child(Panel::new(devices_button)) + .child(Panel::new(difficulty_button)); + + let mining_submenu = OnEventView::new(mining_submenu).on_pre_event(Key::Esc, move |c| { + let _ = c.focus_id(MAIN_MENU); + }); + let table_view = TableView::::new() .column(MiningDeviceColumn::PluginId, "Plugin ID", |c| { @@ -142,12 +214,68 @@ impl TUIStatusListener for TUIMiningView { .child(TextView::new(" ").with_id("network_info")), ); - let mining_view = LinearLayout::new(Orientation::Vertical) + let mining_device_view = LinearLayout::new(Orientation::Vertical) .child(status_view) .child(BoxView::with_full_screen( Dialog::around(table_view.with_id(TABLE_MINING_STATUS).min_size((50, 20))) .title("Mining Devices"), - )); + )) + .with_id("mining_device_view"); + + let diff_status_view = LinearLayout::new(Orientation::Vertical) + .child( + LinearLayout::new(Orientation::Horizontal) + .child(TextView::new("Tip Height: ")) + .child(TextView::new("").with_id("diff_cur_height")), + ) + .child( + LinearLayout::new(Orientation::Horizontal) + .child(TextView::new("Difficulty Adjustment Window: ")) + .child(TextView::new("").with_id("diff_adjust_window")), + ) + .child( + LinearLayout::new(Orientation::Horizontal) + .child(TextView::new("Average Block Time: ")) + .child(TextView::new("").with_id("diff_avg_block_time")), + ) + .child( + LinearLayout::new(Orientation::Horizontal) + .child(TextView::new("Average Difficulty: ")) + .child(TextView::new("").with_id("diff_avg_difficulty")), + ); + + let diff_table_view = TableView::::new() + .column(DiffColumn::BlockNumber, "Block Number", |c| { + c.width_percent(20) + }) + .column(DiffColumn::Index, "Distance from Head", |c| { + c.width_percent(20) + }) + .column(DiffColumn::Difficulty, "Network Difficulty", |c| { + c.width_percent(20) + }) + .column(DiffColumn::Time, "Block Time", |c| c.width_percent(20)) + .column(DiffColumn::Duration, "Duration", |c| c.width_percent(20)); + + let mining_difficulty_view = LinearLayout::new(Orientation::Vertical) + .child(diff_status_view) + .child(BoxView::with_full_screen( + Dialog::around( + diff_table_view + .with_id(TABLE_MINING_DIFF_STATUS) + .min_size((50, 20)), + ).title("Mining Difficulty Data"), + )) + .with_id("mining_difficulty_view"); + + let view_stack = StackView::new() + .layer(mining_difficulty_view) + .layer(mining_device_view) + .with_id("mining_stack_view"); + + let mining_view = LinearLayout::new(Orientation::Vertical) + .child(mining_submenu) + .child(view_stack); Box::new(mining_view.with_id(VIEW_MINING)) } @@ -191,6 +319,7 @@ impl TUIStatusListener for TUIMiningView { } }; + // device c.call_on_id("mining_config_status", |t: &mut TextView| { t.set_content(basic_mining_config_status); }); @@ -201,8 +330,32 @@ impl TUIStatusListener for TUIMiningView { t.set_content(basic_network_info); }); + //diff stats + c.call_on_id("diff_cur_height", |t: &mut TextView| { + t.set_content(stats.diff_stats.height.to_string()); + }); + c.call_on_id("diff_adjust_window", |t: &mut TextView| { + t.set_content(stats.diff_stats.window_size.to_string()); + }); + let dur = time::Duration::from_secs(stats.diff_stats.average_block_time); + c.call_on_id("diff_avg_block_time", |t: &mut TextView| { + t.set_content(format!("{} Secs", dur.as_secs()).to_string()); + }); + c.call_on_id("diff_avg_difficulty", |t: &mut TextView| { + t.set_content(stats.diff_stats.average_difficulty.to_string()); + }); + let mining_stats = stats.mining_stats.clone(); let device_stats = mining_stats.device_stats; + let mut diff_stats = stats.diff_stats.last_blocks.clone(); + diff_stats.reverse(); + let _ = c.call_on_id( + TABLE_MINING_DIFF_STATUS, + |t: &mut TableView| { + t.set_items(diff_stats); + }, + ); + if device_stats.is_none() { return; } diff --git a/src/bin/tui/mod.rs b/src/bin/tui/mod.rs index 96c28150c..421407ccd 100644 --- a/src/bin/tui/mod.rs +++ b/src/bin/tui/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. //! Grin TUI +extern crate chrono; extern crate grin_pow as pow; pub mod ui; diff --git a/src/bin/tui/peers.rs b/src/bin/tui/peers.rs index f8fe87116..25f91bc8f 100644 --- a/src/bin/tui/peers.rs +++ b/src/bin/tui/peers.rs @@ -16,7 +16,7 @@ use std::cmp::Ordering; -use grin::types::{PeerStats, ServerStats}; +use grin::stats::{PeerStats, ServerStats}; use cursive::Cursive; use cursive::view::View; diff --git a/src/bin/tui/types.rs b/src/bin/tui/types.rs index 95c3bc636..ca8fbaeb8 100644 --- a/src/bin/tui/types.rs +++ b/src/bin/tui/types.rs @@ -16,7 +16,7 @@ use cursive::Cursive; use cursive::view::View; -use grin::types::ServerStats; +use grin::stats::ServerStats; /// Main message struct to communicate between the UI and /// the main process diff --git a/util/src/logger.rs b/util/src/logger.rs index 28989ffdc..74877aa9d 100644 --- a/util/src/logger.rs +++ b/util/src/logger.rs @@ -38,6 +38,9 @@ fn convert_log_level(in_level: &LogLevel) -> Level { lazy_static! { /// Flag to observe whether logging was explicitly initialised (don't output otherwise) static ref WAS_INIT: Mutex = Mutex::new(false); + /// Flag to observe whether tui is running, and we therefore don't want to attempt to write + /// panics to stdout + static ref TUI_RUNNING: Mutex = Mutex::new(false); /// Static Logging configuration, should only be set once, before first logging call static ref LOGGING_CONFIG: Mutex = Mutex::new(LoggingConfig::default()); @@ -47,6 +50,10 @@ lazy_static! { let config = LOGGING_CONFIG.lock().unwrap(); let slog_level_stdout = convert_log_level(&config.stdout_log_level); let slog_level_file = convert_log_level(&config.file_log_level); + if config.tui_running.is_some() && config.tui_running.unwrap() { + let mut tui_running_ref = TUI_RUNNING.lock().unwrap(); + *tui_running_ref = true; + } //Terminal output drain let terminal_decorator = slog_term::TermDecorator::new().build(); @@ -141,9 +148,12 @@ fn send_panic_to_log() { ), } //also print to stderr - eprintln!( - "Thread '{}' panicked with message:\n\"{}\"\nSee grin.log for further details.", - thread, msg - ); + let tui_running = TUI_RUNNING.lock().unwrap().clone(); + if !tui_running { + eprintln!( + "Thread '{}' panicked with message:\n\"{}\"\nSee grin.log for further details.", + thread, msg + ); + } })); } diff --git a/util/src/types.rs b/util/src/types.rs index d2d81abe2..31517a971 100644 --- a/util/src/types.rs +++ b/util/src/types.rs @@ -46,6 +46,8 @@ pub struct LoggingConfig { pub log_file_path: String, /// Whether to append to log or replace pub log_file_append: bool, + /// Whether the tui is running (optional) + pub tui_running: Option, } impl Default for LoggingConfig { @@ -57,6 +59,7 @@ impl Default for LoggingConfig { file_log_level: LogLevel::Trace, log_file_path: String::from("grin.log"), log_file_append: false, + tui_running: None, } } }