TUI Difficulty stats for past few blocks (#805)

* added mining subview, changed main menu selection

* collecting difficulty stats from miner

* add diff calc view, separate server stats

* rustfmt

* block difficulty data output

* rustfmt

* ensure diff data is always shown

* don't write to stderr when tui running
This commit is contained in:
Yeastplume 2018-03-19 19:23:58 +00:00 committed by GitHub
parent e312054714
commit 4886fa08b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 454 additions and 152 deletions

View file

@ -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]

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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<ServerStats, Error> {
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<Result<(u64, Difficulty), consensus::TargetError>> =
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<DiffBlock> = 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,
})
}

168
grin/src/stats.rs Normal file
View file

@ -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<AtomicBool>,
/// Mining stats
pub mining_stats: Arc<RwLock<MiningStats>>,
}
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<PeerStats>,
/// 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<Vec<Vec<pow::cuckoo_miner::CuckooMinerDeviceStats>>>,
}
/// 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<DiffBlock>,
/// 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,
}
}
}

View file

@ -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<AtomicBool>,
/// Mining stats
pub mining_stats: Arc<RwLock<MiningStats>>,
}
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<PeerStats>,
}
/// 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<Vec<Vec<pow::cuckoo_miner::CuckooMinerDeviceStats>>>,
}
/// 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,
}
}
}

View file

@ -86,6 +86,10 @@ pub fn mine_genesis_block(
miner_config: Option<types::MinerConfig>,
) -> Result<core::core::Block, Error> {
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();

View file

@ -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(

View file

@ -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

View file

@ -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<View> {
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<View> {
});
};
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<SelectView<&str>> = 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<SelectView<&str>> = 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<SelectView<&str>> = 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))

View file

@ -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<MiningDeviceColumn> 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<DiffColumn> for DiffBlock {
fn to_column(&self, column: DiffColumn) -> String {
let naive_datetime = NaiveDateTime::from_timestamp(self.time as i64, 0);
let datetime: DateTime<Utc> = 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<View> {
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::<CuckooMinerDeviceStats, MiningDeviceColumn>::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::<DiffBlock, DiffColumn>::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<DiffBlock, DiffColumn>| {
t.set_items(diff_stats);
},
);
if device_stats.is_none() {
return;
}

View file

@ -13,6 +13,7 @@
// limitations under the License.
//! Grin TUI
extern crate chrono;
extern crate grin_pow as pow;
pub mod ui;

View file

@ -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;

View file

@ -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

View file

@ -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<bool> = 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<bool> = Mutex::new(false);
/// Static Logging configuration, should only be set once, before first logging call
static ref LOGGING_CONFIG: Mutex<LoggingConfig> = 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
);
}
}));
}

View file

@ -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<bool>,
}
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,
}
}
}