implement the AFL-Style Tui (#1432)
* implement an AFL-Style TUI * improve the tui/mod.rs according to the reviews * fixing fmt manually --------- Co-authored-by: toseven <Byone.heng@gmail.com> Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
parent
1b6ef52a4e
commit
a0bcdfa005
@ -87,12 +87,16 @@ pub struct ClientStats {
|
|||||||
// monitor (maybe we need a separated struct?)
|
// monitor (maybe we need a separated struct?)
|
||||||
/// The corpus size for this client
|
/// The corpus size for this client
|
||||||
pub corpus_size: u64,
|
pub corpus_size: u64,
|
||||||
|
/// The time for the last update of the corpus size
|
||||||
|
pub last_corpus_time: Duration,
|
||||||
/// The total executions for this client
|
/// The total executions for this client
|
||||||
pub executions: u64,
|
pub executions: u64,
|
||||||
/// The number of executions of the previous state in case a client decrease the number of execution (e.g when restarting without saving the state)
|
/// The number of executions of the previous state in case a client decrease the number of execution (e.g when restarting without saving the state)
|
||||||
pub prev_state_executions: u64,
|
pub prev_state_executions: u64,
|
||||||
/// The size of the objectives corpus for this client
|
/// The size of the objectives corpus for this client
|
||||||
pub objective_size: u64,
|
pub objective_size: u64,
|
||||||
|
/// The time for the last update of the objective size
|
||||||
|
pub last_objective_time: Duration,
|
||||||
/// The last reported executions for this client
|
/// The last reported executions for this client
|
||||||
#[cfg(feature = "afl_exec_sec")]
|
#[cfg(feature = "afl_exec_sec")]
|
||||||
pub last_window_executions: u64,
|
pub last_window_executions: u64,
|
||||||
@ -101,6 +105,8 @@ pub struct ClientStats {
|
|||||||
pub last_execs_per_sec: f64,
|
pub last_execs_per_sec: f64,
|
||||||
/// The last time we got this information
|
/// The last time we got this information
|
||||||
pub last_window_time: Duration,
|
pub last_window_time: Duration,
|
||||||
|
/// the start time of the client
|
||||||
|
pub start_time: Duration,
|
||||||
/// User-defined monitor
|
/// User-defined monitor
|
||||||
pub user_monitor: HashMap<String, UserStats>,
|
pub user_monitor: HashMap<String, UserStats>,
|
||||||
/// Client performance statistics
|
/// Client performance statistics
|
||||||
@ -140,6 +146,7 @@ impl ClientStats {
|
|||||||
/// We got a new information about corpus size for this client, insert them.
|
/// We got a new information about corpus size for this client, insert them.
|
||||||
pub fn update_corpus_size(&mut self, corpus_size: u64) {
|
pub fn update_corpus_size(&mut self, corpus_size: u64) {
|
||||||
self.corpus_size = corpus_size;
|
self.corpus_size = corpus_size;
|
||||||
|
self.last_corpus_time = current_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We got a new information about objective corpus size for this client, insert them.
|
/// We got a new information about objective corpus size for this client, insert them.
|
||||||
@ -206,8 +213,9 @@ impl ClientStats {
|
|||||||
self.user_monitor.insert(name, value);
|
self.user_monitor.insert(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
/// Get a user-defined stat using the name
|
/// Get a user-defined stat using the name
|
||||||
pub fn get_user_stats(&mut self, name: &str) -> Option<&UserStats> {
|
pub fn get_user_stats(&self, name: &str) -> Option<&UserStats> {
|
||||||
self.user_monitor.get(name)
|
self.user_monitor.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,6 +283,7 @@ pub trait Monitor {
|
|||||||
for _ in client_stat_count..(client_id.0 + 1) as usize {
|
for _ in client_stat_count..(client_id.0 + 1) as usize {
|
||||||
self.client_stats_mut().push(ClientStats {
|
self.client_stats_mut().push(ClientStats {
|
||||||
last_window_time: current_time(),
|
last_window_time: current_time(),
|
||||||
|
start_time: current_time(),
|
||||||
..ClientStats::default()
|
..ClientStats::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Monitor based on ratatui
|
//! Monitor based on ratatui
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
use alloc::{boxed::Box, string::ToString};
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
@ -22,6 +22,7 @@ use crossterm::{
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use libafl_bolts::{current_time, format_duration_hms, ClientId};
|
use libafl_bolts::{current_time, format_duration_hms, ClientId};
|
||||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
|
use serde_json::{self, Value};
|
||||||
|
|
||||||
#[cfg(feature = "introspection")]
|
#[cfg(feature = "introspection")]
|
||||||
use super::{ClientPerfMonitor, PerfFeature};
|
use super::{ClientPerfMonitor, PerfFeature};
|
||||||
@ -162,14 +163,53 @@ impl PerfTuiContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct ProcessTiming {
|
||||||
|
pub client_start_time: Duration,
|
||||||
|
pub exec_speed: String,
|
||||||
|
pub last_new_entry: Duration,
|
||||||
|
pub last_saved_solution: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessTiming {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
exec_speed: "0".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct ItemGeometry {
|
||||||
|
pub pending: u64,
|
||||||
|
pub pend_fav: u64,
|
||||||
|
pub own_finds: u64,
|
||||||
|
pub imported: u64,
|
||||||
|
pub stability: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemGeometry {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
stability: "0%".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct ClientTuiContext {
|
pub struct ClientTuiContext {
|
||||||
pub corpus: u64,
|
pub corpus: u64,
|
||||||
pub objectives: u64,
|
pub objectives: u64,
|
||||||
pub executions: u64,
|
pub executions: u64,
|
||||||
/// Float value formatted as String
|
/// Float value formatted as String
|
||||||
pub exec_sec: String,
|
pub map_density: String,
|
||||||
|
|
||||||
|
pub cycles_done: u64,
|
||||||
|
|
||||||
|
pub process_timing: ProcessTiming,
|
||||||
|
pub item_geometry: ItemGeometry,
|
||||||
pub user_stats: HashMap<String, UserStats>,
|
pub user_stats: HashMap<String, UserStats>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +218,47 @@ impl ClientTuiContext {
|
|||||||
self.corpus = client.corpus_size;
|
self.corpus = client.corpus_size;
|
||||||
self.objectives = client.objective_size;
|
self.objectives = client.objective_size;
|
||||||
self.executions = client.executions;
|
self.executions = client.executions;
|
||||||
self.exec_sec = exec_sec;
|
self.process_timing.client_start_time = client.start_time;
|
||||||
|
self.process_timing.last_new_entry = if client.last_corpus_time > client.start_time {
|
||||||
|
client.last_corpus_time - client.start_time
|
||||||
|
} else {
|
||||||
|
Duration::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.process_timing.last_saved_solution = if client.last_objective_time > client.start_time
|
||||||
|
{
|
||||||
|
client.last_objective_time - client.start_time
|
||||||
|
} else {
|
||||||
|
Duration::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.process_timing.exec_speed = exec_sec;
|
||||||
|
|
||||||
|
self.map_density = client
|
||||||
|
.get_user_stats("edges")
|
||||||
|
.map_or("0%".to_string(), ToString::to_string);
|
||||||
|
|
||||||
|
let default_json = serde_json::json!({
|
||||||
|
"pending": 0,
|
||||||
|
"pend_fav": 0,
|
||||||
|
"imported": 0,
|
||||||
|
"own_finds": 0,
|
||||||
|
});
|
||||||
|
let afl_stats = client
|
||||||
|
.get_user_stats("AflStats")
|
||||||
|
.map_or(default_json.to_string(), ToString::to_string);
|
||||||
|
|
||||||
|
let afl_stats_json: Value =
|
||||||
|
serde_json::from_str(afl_stats.as_str()).unwrap_or(default_json);
|
||||||
|
self.item_geometry.pending = afl_stats_json["pending"].as_u64().unwrap_or_default();
|
||||||
|
self.item_geometry.pend_fav = afl_stats_json["pend_fav"].as_u64().unwrap_or_default();
|
||||||
|
self.item_geometry.imported = afl_stats_json["imported"].as_u64().unwrap_or_default();
|
||||||
|
self.item_geometry.own_finds = afl_stats_json["own_finds"].as_u64().unwrap_or_default();
|
||||||
|
|
||||||
|
let stability = client
|
||||||
|
.get_user_stats("stability")
|
||||||
|
.map_or("0%".to_string(), ToString::to_string);
|
||||||
|
self.item_geometry.stability = stability;
|
||||||
|
|
||||||
for (key, val) in &client.user_monitor {
|
for (key, val) in &client.user_monitor {
|
||||||
self.user_stats.insert(key.clone(), val.clone());
|
self.user_stats.insert(key.clone(), val.clone());
|
||||||
@ -205,6 +285,14 @@ pub struct TuiContext {
|
|||||||
pub clients_num: usize,
|
pub clients_num: usize,
|
||||||
pub total_execs: u64,
|
pub total_execs: u64,
|
||||||
pub start_time: Duration,
|
pub start_time: Duration,
|
||||||
|
|
||||||
|
pub total_map_density: String,
|
||||||
|
pub total_solutions: u64,
|
||||||
|
pub total_cycles_done: u64,
|
||||||
|
pub total_corpus_count: u64,
|
||||||
|
|
||||||
|
pub total_process_timing: ProcessTiming,
|
||||||
|
pub total_item_geometry: ItemGeometry,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TuiContext {
|
impl TuiContext {
|
||||||
@ -226,6 +314,13 @@ impl TuiContext {
|
|||||||
clients_num: 0,
|
clients_num: 0,
|
||||||
total_execs: 0,
|
total_execs: 0,
|
||||||
start_time,
|
start_time,
|
||||||
|
|
||||||
|
total_map_density: "0%".to_string(),
|
||||||
|
total_solutions: 0,
|
||||||
|
total_cycles_done: 0,
|
||||||
|
total_corpus_count: 0,
|
||||||
|
total_item_geometry: ItemGeometry::new(),
|
||||||
|
total_process_timing: ProcessTiming::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,14 +359,21 @@ impl Monitor for TuiMonitor {
|
|||||||
let execsec = self.execs_per_sec() as u64;
|
let execsec = self.execs_per_sec() as u64;
|
||||||
let totalexec = self.total_execs();
|
let totalexec = self.total_execs();
|
||||||
let run_time = cur_time - self.start_time;
|
let run_time = cur_time - self.start_time;
|
||||||
|
let total_process_timing = self.process_timing();
|
||||||
|
|
||||||
let mut ctx = self.context.write().unwrap();
|
let mut ctx = self.context.write().unwrap();
|
||||||
|
ctx.total_process_timing = total_process_timing;
|
||||||
ctx.corpus_size_timed.add(run_time, self.corpus_size());
|
ctx.corpus_size_timed.add(run_time, self.corpus_size());
|
||||||
ctx.objective_size_timed
|
ctx.objective_size_timed
|
||||||
.add(run_time, self.objective_size());
|
.add(run_time, self.objective_size());
|
||||||
ctx.execs_per_sec_timed.add(run_time, execsec);
|
ctx.execs_per_sec_timed.add(run_time, execsec);
|
||||||
ctx.total_execs = totalexec;
|
ctx.total_execs = totalexec;
|
||||||
ctx.clients_num = self.client_stats.len();
|
ctx.clients_num = self.client_stats.len();
|
||||||
|
ctx.total_map_density = self.map_density();
|
||||||
|
ctx.total_solutions = self.objective_size();
|
||||||
|
ctx.total_cycles_done = 0;
|
||||||
|
ctx.total_corpus_count = self.corpus_size();
|
||||||
|
ctx.total_item_geometry = self.item_geometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = self.client_stats_mut_for(sender_id);
|
let client = self.client_stats_mut_for(sender_id);
|
||||||
@ -339,6 +441,91 @@ impl TuiMonitor {
|
|||||||
client_stats: vec![],
|
client_stats: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_density(&self) -> String {
|
||||||
|
if self.client_stats.len() < 2 {
|
||||||
|
return "0%".to_string();
|
||||||
|
}
|
||||||
|
let mut max_map_density = self
|
||||||
|
.client_stats()
|
||||||
|
.get(1)
|
||||||
|
.unwrap()
|
||||||
|
.get_user_stats("edges")
|
||||||
|
.map_or("0%".to_string(), ToString::to_string);
|
||||||
|
|
||||||
|
for client in self.client_stats().iter().skip(2) {
|
||||||
|
let client_map_density = client
|
||||||
|
.get_user_stats("edges")
|
||||||
|
.map_or(String::new(), ToString::to_string);
|
||||||
|
if client_map_density > max_map_density {
|
||||||
|
max_map_density = client_map_density;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max_map_density
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item_geometry(&self) -> ItemGeometry {
|
||||||
|
let mut total_item_geometry = ItemGeometry::new();
|
||||||
|
if self.client_stats.len() < 2 {
|
||||||
|
return total_item_geometry;
|
||||||
|
}
|
||||||
|
let mut ratio_a: u64 = 0;
|
||||||
|
let mut ratio_b: u64 = 0;
|
||||||
|
for client in self.client_stats().iter().skip(1) {
|
||||||
|
let afl_stats = client
|
||||||
|
.get_user_stats("AflStats")
|
||||||
|
.map_or("None".to_string(), ToString::to_string);
|
||||||
|
let stability = client
|
||||||
|
.get_user_stats("stability")
|
||||||
|
.map_or(&UserStats::Ratio(0, 100), |x| x);
|
||||||
|
|
||||||
|
if afl_stats != "None" {
|
||||||
|
let default_json = serde_json::json!({
|
||||||
|
"pending": 0,
|
||||||
|
"pend_fav": 0,
|
||||||
|
"imported": 0,
|
||||||
|
"own_finds": 0,
|
||||||
|
});
|
||||||
|
let afl_stats_json: Value =
|
||||||
|
serde_json::from_str(afl_stats.as_str()).unwrap_or(default_json);
|
||||||
|
total_item_geometry.pending +=
|
||||||
|
afl_stats_json["pending"].as_u64().unwrap_or_default();
|
||||||
|
total_item_geometry.pend_fav +=
|
||||||
|
afl_stats_json["pend_fav"].as_u64().unwrap_or_default();
|
||||||
|
total_item_geometry.own_finds +=
|
||||||
|
afl_stats_json["own_finds"].as_u64().unwrap_or_default();
|
||||||
|
total_item_geometry.imported +=
|
||||||
|
afl_stats_json["imported"].as_u64().unwrap_or_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let UserStats::Ratio(a, b) = stability {
|
||||||
|
ratio_a += a;
|
||||||
|
ratio_b += b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total_item_geometry.stability = format!("{}%", ratio_a * 100 / ratio_b);
|
||||||
|
total_item_geometry
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_timing(&mut self) -> ProcessTiming {
|
||||||
|
let mut total_process_timing = ProcessTiming::new();
|
||||||
|
total_process_timing.exec_speed = self.execs_per_sec_pretty();
|
||||||
|
if self.client_stats.len() > 1 {
|
||||||
|
let mut new_path_time = Duration::default();
|
||||||
|
let mut new_objectives_time = Duration::default();
|
||||||
|
for client in self.client_stats().iter().skip(1) {
|
||||||
|
new_path_time = client.last_corpus_time.max(new_path_time);
|
||||||
|
new_objectives_time = client.last_objective_time.max(new_objectives_time);
|
||||||
|
}
|
||||||
|
if new_path_time > self.start_time {
|
||||||
|
total_process_timing.last_new_entry = new_path_time - self.start_time;
|
||||||
|
}
|
||||||
|
if new_objectives_time > self.start_time {
|
||||||
|
total_process_timing.last_saved_solution = new_objectives_time - self.start_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total_process_timing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_tui_thread(context: Arc<RwLock<TuiContext>>, tick_rate: Duration, tui_ui: TuiUI) {
|
fn run_tui_thread(context: Arc<RwLock<TuiContext>>, tick_rate: Duration, tui_ui: TuiUI) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use alloc::vec::Vec;
|
use alloc::{string::ToString, vec::Vec};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{max, min},
|
cmp::{max, min},
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
@ -16,7 +16,10 @@ use ratatui::{
|
|||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{current_time, format_duration_hms, Duration, String, TimedStats, TuiContext};
|
use super::{
|
||||||
|
current_time, format_duration_hms, Duration, ItemGeometry, ProcessTiming, String, TimedStats,
|
||||||
|
TuiContext,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct TuiUI {
|
pub struct TuiUI {
|
||||||
@ -99,21 +102,59 @@ impl TuiUI {
|
|||||||
|
|
||||||
let body = Layout::default()
|
let body = Layout::default()
|
||||||
.constraints(if self.show_logs {
|
.constraints(if self.show_logs {
|
||||||
[Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()
|
if cfg!(feature = "introspection") {
|
||||||
|
[
|
||||||
|
Constraint::Percentage(41),
|
||||||
|
Constraint::Percentage(44),
|
||||||
|
Constraint::Percentage(15),
|
||||||
|
]
|
||||||
|
.as_ref()
|
||||||
|
} else {
|
||||||
|
[
|
||||||
|
Constraint::Percentage(41),
|
||||||
|
Constraint::Percentage(27),
|
||||||
|
Constraint::Percentage(32),
|
||||||
|
]
|
||||||
|
.as_ref()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
[Constraint::Percentage(100)].as_ref()
|
[Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()
|
||||||
})
|
})
|
||||||
.split(f.size());
|
.split(f.size());
|
||||||
|
let top_body = body[0];
|
||||||
|
let mid_body = body[1];
|
||||||
|
|
||||||
|
self.draw_overall_ui(f, app, top_body);
|
||||||
|
self.draw_client_ui(f, app, mid_body);
|
||||||
|
|
||||||
|
if self.show_logs {
|
||||||
|
let bottom_body = body[2];
|
||||||
|
self.draw_logs(f, app, bottom_body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn draw_overall_ui<B>(&mut self, f: &mut Frame<B>, app: &Arc<RwLock<TuiContext>>, area: Rect)
|
||||||
|
where
|
||||||
|
B: Backend,
|
||||||
|
{
|
||||||
let top_layout = Layout::default()
|
let top_layout = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Vertical)
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
.constraints([Constraint::Length(16), Constraint::Min(0)].as_ref())
|
||||||
.split(body[0]);
|
.split(area);
|
||||||
|
let bottom_layout = top_layout[1];
|
||||||
|
|
||||||
let left_layout = Layout::default()
|
let left_top_layout = Layout::default()
|
||||||
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(40), Constraint::Percentage(60)].as_ref())
|
||||||
.split(top_layout[0]);
|
.split(top_layout[0]);
|
||||||
|
|
||||||
|
let right_top_layout = left_top_layout[1];
|
||||||
|
|
||||||
|
let title_layout = Layout::default()
|
||||||
|
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
|
||||||
|
.split(left_top_layout[0]);
|
||||||
|
|
||||||
let mut status_bar: String = self.title.clone();
|
let mut status_bar: String = self.title.clone();
|
||||||
status_bar = status_bar + " (" + self.version.as_str() + ")";
|
status_bar = status_bar + " (" + self.version.as_str() + ")";
|
||||||
|
|
||||||
@ -126,14 +167,24 @@ impl TuiUI {
|
|||||||
let block = Block::default().borders(Borders::ALL);
|
let block = Block::default().borders(Borders::ALL);
|
||||||
let paragraph = Paragraph::new(text)
|
let paragraph = Paragraph::new(text)
|
||||||
.block(block)
|
.block(block)
|
||||||
.alignment(Alignment::Center); //.wrap(Wrap { trim: true });
|
.alignment(Alignment::Center);
|
||||||
f.render_widget(paragraph, left_layout[0]);
|
f.render_widget(paragraph, title_layout[0]);
|
||||||
|
|
||||||
self.draw_text(f, app, left_layout[1]);
|
let process_timting_layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Length(6), Constraint::Min(0)].as_ref())
|
||||||
|
.split(title_layout[1]);
|
||||||
|
self.draw_process_timing_text(f, app, process_timting_layout[0], true);
|
||||||
|
|
||||||
let right_layout = Layout::default()
|
let path_geometry_layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Length(7), Constraint::Min(0)].as_ref())
|
||||||
|
.split(process_timting_layout[1]);
|
||||||
|
self.draw_item_geometry_text(f, app, path_geometry_layout[0], true);
|
||||||
|
|
||||||
|
let title_chart_layout = Layout::default()
|
||||||
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
|
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
|
||||||
.split(top_layout[1]);
|
.split(right_top_layout);
|
||||||
let titles = vec![
|
let titles = vec![
|
||||||
Line::from(Span::styled(
|
Line::from(Span::styled(
|
||||||
"speed",
|
"speed",
|
||||||
@ -144,7 +195,7 @@ impl TuiUI {
|
|||||||
Style::default().fg(Color::LightGreen),
|
Style::default().fg(Color::LightGreen),
|
||||||
)),
|
)),
|
||||||
Line::from(Span::styled(
|
Line::from(Span::styled(
|
||||||
"objectives",
|
"objectives (`g` switch)",
|
||||||
Style::default().fg(Color::LightGreen),
|
Style::default().fg(Color::LightGreen),
|
||||||
)),
|
)),
|
||||||
];
|
];
|
||||||
@ -152,7 +203,7 @@ impl TuiUI {
|
|||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.title(Span::styled(
|
.title(Span::styled(
|
||||||
"charts (`g` switch)",
|
"charts",
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::LightCyan)
|
.fg(Color::LightCyan)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
@ -161,7 +212,9 @@ impl TuiUI {
|
|||||||
)
|
)
|
||||||
.highlight_style(Style::default().fg(Color::LightYellow))
|
.highlight_style(Style::default().fg(Color::LightYellow))
|
||||||
.select(self.charts_tab_idx);
|
.select(self.charts_tab_idx);
|
||||||
f.render_widget(tabs, right_layout[0]);
|
f.render_widget(tabs, title_chart_layout[0]);
|
||||||
|
|
||||||
|
let chart_layout = title_chart_layout[1];
|
||||||
|
|
||||||
match self.charts_tab_idx {
|
match self.charts_tab_idx {
|
||||||
0 => {
|
0 => {
|
||||||
@ -170,7 +223,7 @@ impl TuiUI {
|
|||||||
"speed chart",
|
"speed chart",
|
||||||
"exec/sec",
|
"exec/sec",
|
||||||
f,
|
f,
|
||||||
right_layout[1],
|
chart_layout,
|
||||||
&ctx.execs_per_sec_timed,
|
&ctx.execs_per_sec_timed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -180,7 +233,7 @@ impl TuiUI {
|
|||||||
"corpus chart",
|
"corpus chart",
|
||||||
"corpus size",
|
"corpus size",
|
||||||
f,
|
f,
|
||||||
right_layout[1],
|
chart_layout,
|
||||||
&ctx.corpus_size_timed,
|
&ctx.corpus_size_timed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -190,16 +243,60 @@ impl TuiUI {
|
|||||||
"corpus chart",
|
"corpus chart",
|
||||||
"objectives",
|
"objectives",
|
||||||
f,
|
f,
|
||||||
right_layout[1],
|
chart_layout,
|
||||||
&ctx.objective_size_timed,
|
&ctx.objective_size_timed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
self.draw_overall_generic_text(f, app, bottom_layout);
|
||||||
|
}
|
||||||
|
|
||||||
if self.show_logs {
|
fn draw_client_ui<B>(&mut self, f: &mut Frame<B>, app: &Arc<RwLock<TuiContext>>, area: Rect)
|
||||||
self.draw_logs(f, app, body[1]);
|
where
|
||||||
|
B: Backend,
|
||||||
|
{
|
||||||
|
let client_block = Block::default()
|
||||||
|
.title(Span::styled(
|
||||||
|
format!("client #{} (l/r arrows to switch)", self.clients_idx),
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightCyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
))
|
||||||
|
.borders(Borders::ALL);
|
||||||
|
let client_area = client_block.inner(area);
|
||||||
|
f.render_widget(client_block, area);
|
||||||
|
|
||||||
|
#[cfg(feature = "introspection")]
|
||||||
|
{
|
||||||
|
let introspection_layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Length(11), Constraint::Min(0)].as_ref())
|
||||||
|
.split(client_area)[1];
|
||||||
|
self.draw_introspection_text(f, app, introspection_layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let left_layout = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
|
.split(client_area);
|
||||||
|
let right_layout = left_layout[1];
|
||||||
|
|
||||||
|
let left_top_layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Length(6), Constraint::Length(0)].as_ref())
|
||||||
|
.split(left_layout[0]);
|
||||||
|
let left_bottom_layout = left_top_layout[1];
|
||||||
|
self.draw_process_timing_text(f, app, left_top_layout[0], false);
|
||||||
|
self.draw_client_generic_text(f, app, left_bottom_layout);
|
||||||
|
|
||||||
|
let right_top_layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Length(7), Constraint::Length(0)].as_ref())
|
||||||
|
.split(right_layout);
|
||||||
|
let right_bottom_layout = right_top_layout[1];
|
||||||
|
self.draw_item_geometry_text(f, app, right_top_layout[0], false);
|
||||||
|
self.draw_client_results_text(f, app, right_bottom_layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines, clippy::cast_precision_loss)]
|
#[allow(clippy::too_many_lines, clippy::cast_precision_loss)]
|
||||||
@ -329,37 +426,49 @@ impl TuiUI {
|
|||||||
f.render_widget(chart, area);
|
f.render_widget(chart, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
fn draw_item_geometry_text<B>(
|
||||||
fn draw_text<B>(&mut self, f: &mut Frame<B>, app: &Arc<RwLock<TuiContext>>, area: Rect)
|
&mut self,
|
||||||
where
|
f: &mut Frame<B>,
|
||||||
|
app: &Arc<RwLock<TuiContext>>,
|
||||||
|
area: Rect,
|
||||||
|
is_overall: bool,
|
||||||
|
) where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
|
let item_geometry: ItemGeometry = if is_overall {
|
||||||
|
app.read().unwrap().total_item_geometry.clone()
|
||||||
|
} else if self.clients < 2 {
|
||||||
|
ItemGeometry::new()
|
||||||
|
} else {
|
||||||
|
app.read()
|
||||||
|
.unwrap()
|
||||||
|
.clients
|
||||||
|
.get(&self.clients_idx)
|
||||||
|
.unwrap()
|
||||||
|
.item_geometry
|
||||||
|
.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let items = vec![
|
let items = vec![
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(Span::raw("run time")),
|
Cell::from(Span::raw("pending")),
|
||||||
Cell::from(Span::raw(format_duration_hms(
|
Cell::from(Span::raw(format!("{}", item_geometry.pending))),
|
||||||
&(current_time() - app.read().unwrap().start_time),
|
|
||||||
))),
|
|
||||||
]),
|
]),
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(Span::raw("clients")),
|
Cell::from(Span::raw("pend fav")),
|
||||||
Cell::from(Span::raw(format!("{}", self.clients))),
|
Cell::from(Span::raw(format!("{}", item_geometry.pend_fav))),
|
||||||
]),
|
]),
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(Span::raw("executions")),
|
Cell::from(Span::raw("own finds")),
|
||||||
Cell::from(Span::raw(format!("{}", app.read().unwrap().total_execs))),
|
Cell::from(Span::raw(format!("{}", item_geometry.own_finds))),
|
||||||
]),
|
]),
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(Span::raw("exec/sec")),
|
Cell::from(Span::raw("imported")),
|
||||||
Cell::from(Span::raw(format!(
|
Cell::from(Span::raw(format!("{}", item_geometry.imported))),
|
||||||
"{}",
|
]),
|
||||||
app.read()
|
Row::new(vec![
|
||||||
.unwrap()
|
Cell::from(Span::raw("stability")),
|
||||||
.execs_per_sec_timed
|
Cell::from(Span::raw(item_geometry.stability)),
|
||||||
.series
|
|
||||||
.back()
|
|
||||||
.map_or(0, |x| x.item)
|
|
||||||
))),
|
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -373,6 +482,274 @@ impl TuiUI {
|
|||||||
)
|
)
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
|
let table = Table::new(items)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title(Span::styled(
|
||||||
|
"item geometry",
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightCyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
)
|
||||||
|
.widths(&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||||
|
f.render_widget(table, chunks[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_process_timing_text<B>(
|
||||||
|
&mut self,
|
||||||
|
f: &mut Frame<B>,
|
||||||
|
app: &Arc<RwLock<TuiContext>>,
|
||||||
|
area: Rect,
|
||||||
|
is_overall: bool,
|
||||||
|
) where
|
||||||
|
B: Backend,
|
||||||
|
{
|
||||||
|
let tup: (Duration, ProcessTiming) = if is_overall {
|
||||||
|
let tui_context = app.read().unwrap();
|
||||||
|
(
|
||||||
|
tui_context.start_time,
|
||||||
|
tui_context.total_process_timing.clone(),
|
||||||
|
)
|
||||||
|
} else if self.clients < 2 {
|
||||||
|
(current_time(), ProcessTiming::new())
|
||||||
|
} else {
|
||||||
|
let client = app
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.clients
|
||||||
|
.get(&self.clients_idx)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
(
|
||||||
|
client.process_timing.client_start_time,
|
||||||
|
client.process_timing,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let items = vec![
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("run time")),
|
||||||
|
Cell::from(Span::raw(format_duration_hms(&(current_time() - tup.0)))),
|
||||||
|
]),
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("exec speed")),
|
||||||
|
Cell::from(Span::raw(tup.1.exec_speed)),
|
||||||
|
]),
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("last new entry")),
|
||||||
|
Cell::from(Span::raw(format_duration_hms(&(tup.1.last_new_entry)))),
|
||||||
|
]),
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("last solution")),
|
||||||
|
Cell::from(Span::raw(format_duration_hms(&(tup.1.last_saved_solution)))),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(2 + items.len() as u16),
|
||||||
|
Constraint::Min(0),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
let table = Table::new(items)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title(Span::styled(
|
||||||
|
"process timing",
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightCyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
)
|
||||||
|
.widths(&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||||
|
f.render_widget(table, chunks[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_overall_generic_text<B>(
|
||||||
|
&mut self,
|
||||||
|
f: &mut Frame<B>,
|
||||||
|
app: &Arc<RwLock<TuiContext>>,
|
||||||
|
area: Rect,
|
||||||
|
) where
|
||||||
|
B: Backend,
|
||||||
|
{
|
||||||
|
let items = vec![
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("clients")),
|
||||||
|
Cell::from(Span::raw(format!("{}", self.clients))),
|
||||||
|
Cell::from(Span::raw("total execs")),
|
||||||
|
Cell::from(Span::raw(format!("{}", app.read().unwrap().total_execs))),
|
||||||
|
Cell::from(Span::raw("map density")),
|
||||||
|
Cell::from(Span::raw(app.read().unwrap().total_map_density.to_string())),
|
||||||
|
]),
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("solutions")),
|
||||||
|
Cell::from(Span::raw(format!(
|
||||||
|
"{}",
|
||||||
|
app.read().unwrap().total_solutions
|
||||||
|
))),
|
||||||
|
Cell::from(Span::raw("cycle done")),
|
||||||
|
Cell::from(Span::raw(format!(
|
||||||
|
"{}",
|
||||||
|
app.read().unwrap().total_cycles_done
|
||||||
|
))),
|
||||||
|
Cell::from(Span::raw("corpus count")),
|
||||||
|
Cell::from(Span::raw(format!(
|
||||||
|
"{}",
|
||||||
|
app.read().unwrap().total_corpus_count
|
||||||
|
))),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(2 + items.len() as u16),
|
||||||
|
Constraint::Min(0),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
let table = Table::new(items)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title(Span::styled(
|
||||||
|
"generic",
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightCyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
)
|
||||||
|
.widths(&[
|
||||||
|
Constraint::Percentage(15),
|
||||||
|
Constraint::Percentage(16),
|
||||||
|
Constraint::Percentage(15),
|
||||||
|
Constraint::Percentage(16),
|
||||||
|
Constraint::Percentage(15),
|
||||||
|
Constraint::Percentage(27),
|
||||||
|
]);
|
||||||
|
f.render_widget(table, chunks[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_client_results_text<B>(
|
||||||
|
&mut self,
|
||||||
|
f: &mut Frame<B>,
|
||||||
|
app: &Arc<RwLock<TuiContext>>,
|
||||||
|
area: Rect,
|
||||||
|
) where
|
||||||
|
B: Backend,
|
||||||
|
{
|
||||||
|
let items = vec![
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("cycles done")),
|
||||||
|
Cell::from(Span::raw(format!(
|
||||||
|
"{}",
|
||||||
|
app.read()
|
||||||
|
.unwrap()
|
||||||
|
.clients
|
||||||
|
.get(&self.clients_idx)
|
||||||
|
.map_or(0, |x| x.cycles_done)
|
||||||
|
))),
|
||||||
|
]),
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("solutions")),
|
||||||
|
Cell::from(Span::raw(format!(
|
||||||
|
"{}",
|
||||||
|
app.read()
|
||||||
|
.unwrap()
|
||||||
|
.clients
|
||||||
|
.get(&self.clients_idx)
|
||||||
|
.map_or(0, |x| x.objectives)
|
||||||
|
))),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(2 + items.len() as u16),
|
||||||
|
Constraint::Min(0),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
let table = Table::new(items)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title(Span::styled(
|
||||||
|
"overall results",
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightCyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
)
|
||||||
|
.widths(&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||||
|
f.render_widget(table, chunks[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_client_generic_text<B>(
|
||||||
|
&mut self,
|
||||||
|
f: &mut Frame<B>,
|
||||||
|
app: &Arc<RwLock<TuiContext>>,
|
||||||
|
area: Rect,
|
||||||
|
) where
|
||||||
|
B: Backend,
|
||||||
|
{
|
||||||
|
let items = vec![
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("corpus count")),
|
||||||
|
Cell::from(Span::raw(format!(
|
||||||
|
"{}",
|
||||||
|
app.read()
|
||||||
|
.unwrap()
|
||||||
|
.clients
|
||||||
|
.get(&self.clients_idx)
|
||||||
|
.map_or(0, |x| x.corpus)
|
||||||
|
))),
|
||||||
|
]),
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("total execs")),
|
||||||
|
Cell::from(Span::raw(format!(
|
||||||
|
"{}",
|
||||||
|
app.read()
|
||||||
|
.unwrap()
|
||||||
|
.clients
|
||||||
|
.get(&self.clients_idx)
|
||||||
|
.map_or(0, |x| x.executions)
|
||||||
|
))),
|
||||||
|
]),
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(Span::raw("map density")),
|
||||||
|
Cell::from(Span::raw(
|
||||||
|
app.read()
|
||||||
|
.unwrap()
|
||||||
|
.clients
|
||||||
|
.get(&self.clients_idx)
|
||||||
|
.map_or("0%".to_string(), |x| x.map_density.to_string()),
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(2 + items.len() as u16),
|
||||||
|
Constraint::Min(0),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(area);
|
||||||
|
|
||||||
let table = Table::new(items)
|
let table = Table::new(items)
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
@ -386,123 +763,69 @@ impl TuiUI {
|
|||||||
)
|
)
|
||||||
.widths(&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
.widths(&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||||
f.render_widget(table, chunks[0]);
|
f.render_widget(table, chunks[0]);
|
||||||
|
}
|
||||||
|
|
||||||
let client_block = Block::default()
|
#[cfg(feature = "introspection")]
|
||||||
.title(Span::styled(
|
fn draw_introspection_text<B>(
|
||||||
format!("client #{} (l/r arrows to switch)", self.clients_idx),
|
&mut self,
|
||||||
Style::default()
|
f: &mut Frame<B>,
|
||||||
.fg(Color::LightCyan)
|
app: &Arc<RwLock<TuiContext>>,
|
||||||
.add_modifier(Modifier::BOLD),
|
area: Rect,
|
||||||
))
|
) where
|
||||||
.borders(Borders::ALL);
|
B: Backend,
|
||||||
let client_area = client_block.inner(chunks[1]);
|
{
|
||||||
f.render_widget(client_block, chunks[1]);
|
let mut items = vec![];
|
||||||
|
|
||||||
let mut client_items = vec![];
|
|
||||||
{
|
{
|
||||||
let ctx = app.read().unwrap();
|
let ctx = app.read().unwrap();
|
||||||
if let Some(client) = ctx.clients.get(&self.clients_idx) {
|
if let Some(client) = ctx.introspection.get(&self.clients_idx) {
|
||||||
client_items.push(Row::new(vec![
|
items.push(Row::new(vec![
|
||||||
Cell::from(Span::raw("executions")),
|
Cell::from(Span::raw("scheduler")),
|
||||||
Cell::from(Span::raw(format!("{}", client.executions))),
|
Cell::from(Span::raw(format!("{:.2}%", client.scheduler * 100.0))),
|
||||||
]));
|
]));
|
||||||
client_items.push(Row::new(vec![
|
items.push(Row::new(vec![
|
||||||
Cell::from(Span::raw("exec/sec")),
|
Cell::from(Span::raw("manager")),
|
||||||
Cell::from(Span::raw(client.exec_sec.clone())),
|
Cell::from(Span::raw(format!("{:.2}%", client.manager * 100.0))),
|
||||||
]));
|
]));
|
||||||
client_items.push(Row::new(vec![
|
for i in 0..client.stages.len() {
|
||||||
Cell::from(Span::raw("corpus")),
|
|
||||||
Cell::from(Span::raw(format!("{}", client.corpus))),
|
|
||||||
]));
|
|
||||||
client_items.push(Row::new(vec![
|
|
||||||
Cell::from(Span::raw("objectives")),
|
|
||||||
Cell::from(Span::raw(format!("{}", client.objectives))),
|
|
||||||
]));
|
|
||||||
for (key, val) in &client.user_stats {
|
|
||||||
client_items.push(Row::new(vec![
|
|
||||||
Cell::from(Span::raw(key.clone())),
|
|
||||||
Cell::from(Span::raw(format!("{}", val.clone()))),
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "introspection")]
|
|
||||||
let client_chunks = Layout::default()
|
|
||||||
.constraints(
|
|
||||||
[
|
|
||||||
Constraint::Length(client_items.len() as u16),
|
|
||||||
Constraint::Min(4),
|
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(client_area);
|
|
||||||
#[cfg(not(feature = "introspection"))]
|
|
||||||
let client_chunks = Layout::default()
|
|
||||||
.constraints([Constraint::Percentage(100)].as_ref())
|
|
||||||
.split(client_area);
|
|
||||||
|
|
||||||
let table = Table::new(client_items)
|
|
||||||
.block(Block::default())
|
|
||||||
.widths(&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
|
||||||
f.render_widget(table, client_chunks[0]);
|
|
||||||
|
|
||||||
#[cfg(feature = "introspection")]
|
|
||||||
{
|
|
||||||
let mut items = vec![];
|
|
||||||
{
|
|
||||||
let ctx = app.read().unwrap();
|
|
||||||
if let Some(client) = ctx.introspection.get(&self.clients_idx) {
|
|
||||||
items.push(Row::new(vec![
|
items.push(Row::new(vec![
|
||||||
Cell::from(Span::raw("scheduler")),
|
Cell::from(Span::raw(format!("stage {i}"))),
|
||||||
Cell::from(Span::raw(format!("{:.2}%", client.scheduler * 100.0))),
|
Cell::from(Span::raw("")),
|
||||||
]));
|
]));
|
||||||
items.push(Row::new(vec![
|
|
||||||
Cell::from(Span::raw("manager")),
|
|
||||||
Cell::from(Span::raw(format!("{:.2}%", client.manager * 100.0))),
|
|
||||||
]));
|
|
||||||
for i in 0..client.stages.len() {
|
|
||||||
items.push(Row::new(vec![
|
|
||||||
Cell::from(Span::raw(format!("stage {i}"))),
|
|
||||||
Cell::from(Span::raw("")),
|
|
||||||
]));
|
|
||||||
|
|
||||||
for (key, val) in &client.stages[i] {
|
for (key, val) in &client.stages[i] {
|
||||||
items.push(Row::new(vec![
|
|
||||||
Cell::from(Span::raw(key.clone())),
|
|
||||||
Cell::from(Span::raw(format!("{:.2}%", val * 100.0))),
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (key, val) in &client.feedbacks {
|
|
||||||
items.push(Row::new(vec![
|
items.push(Row::new(vec![
|
||||||
Cell::from(Span::raw(key.clone())),
|
Cell::from(Span::raw(key.clone())),
|
||||||
Cell::from(Span::raw(format!("{:.2}%", val * 100.0))),
|
Cell::from(Span::raw(format!("{:.2}%", val * 100.0))),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
for (key, val) in &client.feedbacks {
|
||||||
items.push(Row::new(vec![
|
items.push(Row::new(vec![
|
||||||
Cell::from(Span::raw("not measured")),
|
Cell::from(Span::raw(key.clone())),
|
||||||
Cell::from(Span::raw(format!("{:.2}%", client.unmeasured * 100.0))),
|
Cell::from(Span::raw(format!("{:.2}%", val * 100.0))),
|
||||||
]));
|
]));
|
||||||
};
|
}
|
||||||
}
|
items.push(Row::new(vec![
|
||||||
|
Cell::from(Span::raw("not measured")),
|
||||||
let table = Table::new(items)
|
Cell::from(Span::raw(format!("{:.2}%", client.unmeasured * 100.0))),
|
||||||
.block(
|
]));
|
||||||
Block::default()
|
};
|
||||||
.title(Span::styled(
|
|
||||||
"introspection",
|
|
||||||
Style::default()
|
|
||||||
.fg(Color::LightCyan)
|
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
))
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.widths(&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
|
||||||
f.render_widget(table, client_chunks[1]);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
let table = Table::new(items)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title(Span::styled(
|
||||||
|
"introspection",
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightCyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
)
|
||||||
|
.widths(&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||||
|
f.render_widget(table, area);
|
||||||
|
}
|
||||||
#[allow(clippy::unused_self)]
|
#[allow(clippy::unused_self)]
|
||||||
fn draw_logs<B>(&mut self, f: &mut Frame<B>, app: &Arc<RwLock<TuiContext>>, area: Rect)
|
fn draw_logs<B>(&mut self, f: &mut Frame<B>, app: &Arc<RwLock<TuiContext>>, area: Rect)
|
||||||
where
|
where
|
||||||
|
Loading…
x
Reference in New Issue
Block a user