Skip to content

Commit 38edb4f

Browse files
author
vidishagawas121
committed
feat: Add TUI improvements including task list sorting, dynamic column resizing, and performance optimizations
1 parent 2bd1afd commit 38edb4f

File tree

6 files changed

+362
-100
lines changed

6 files changed

+362
-100
lines changed

Diff for: tokio-console/src/input.rs

+84
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,90 @@ pub(crate) fn is_esc(event: &Event) -> bool {
6666
)
6767
}
6868

69+
#[derive(Debug, Clone)]
70+
pub(crate) enum Event {
71+
Key(KeyEvent),
72+
Mouse(MouseEvent),
73+
}
74+
75+
#[derive(Debug, Clone)]
76+
pub(crate) struct KeyEvent {
77+
pub code: KeyCode,
78+
pub modifiers: KeyModifiers,
79+
}
80+
81+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82+
pub(crate) enum KeyCode {
83+
Char(char),
84+
Enter,
85+
Esc,
86+
Backspace,
87+
Left,
88+
Right,
89+
Up,
90+
Down,
91+
Home,
92+
End,
93+
PageUp,
94+
PageDown,
95+
Tab,
96+
BackTab,
97+
Delete,
98+
Insert,
99+
F(u8),
100+
}
101+
102+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103+
pub(crate) struct KeyModifiers {
104+
pub shift: bool,
105+
pub control: bool,
106+
pub alt: bool,
107+
pub super_: bool,
108+
}
109+
110+
impl Default for KeyModifiers {
111+
fn default() -> Self {
112+
Self {
113+
shift: false,
114+
control: false,
115+
alt: false,
116+
super_: false,
117+
}
118+
}
119+
}
120+
121+
pub(crate) fn poll(dur: Duration) -> std::io::Result<Option<Event>> {
122+
if crossterm::event::poll(dur)? {
123+
let event = crossterm::event::read()?;
124+
Ok(Some(convert_event(event)))
125+
} else {
126+
Ok(None)
127+
}
128+
}
129+
130+
fn convert_event(event: Event) -> Event {
131+
match event {
132+
Event::Key(key) => Event::Key(KeyEvent {
133+
code: convert_key_code(key.code),
134+
modifiers: KeyModifiers {
135+
shift: key.modifiers.contains(KeyModifiers::shift),
136+
control: key.modifiers.contains(KeyModifiers::control),
137+
alt: key.modifiers.contains(KeyModifiers::alt),
138+
super_: key.modifiers.contains(KeyModifiers::super_),
139+
},
140+
}),
141+
Event::Mouse(mouse) => Event::Mouse(mouse),
142+
_ => Event::Key(KeyEvent {
143+
code: KeyCode::Char(' '),
144+
modifiers: KeyModifiers::default(),
145+
}),
146+
}
147+
}
148+
149+
fn convert_key_code(code: KeyCode) -> KeyCode {
150+
code
151+
}
152+
69153
#[cfg(test)]
70154
mod tests {
71155
use super::*;

Diff for: tokio-console/src/state/mod.rs

+73-5
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ use std::{
1616
convert::{TryFrom, TryInto},
1717
fmt,
1818
rc::Rc,
19+
sync::atomic::{AtomicU64, Ordering},
1920
time::{Duration, SystemTime},
21+
vec::Vec,
2022
};
2123
use tasks::{Details, Task, TasksState};
24+
use tokio::sync::watch;
2225

2326
pub mod async_ops;
2427
pub mod histogram;
@@ -30,7 +33,9 @@ pub(crate) use self::store::Id;
3033

3134
pub(crate) type DetailsRef = Rc<RefCell<Option<Details>>>;
3235

33-
#[derive(Default, Debug)]
36+
const UPDATE_BUFFER_SIZE: usize = 1000;
37+
const UPDATE_BATCH_INTERVAL: Duration = Duration::from_millis(16); // ~60fps
38+
3439
pub(crate) struct State {
3540
metas: HashMap<u64, Metadata>,
3641
last_updated_at: Option<SystemTime>,
@@ -41,6 +46,10 @@ pub(crate) struct State {
4146
current_task_details: DetailsRef,
4247
retain_for: Option<Duration>,
4348
strings: intern::Strings,
49+
last_update: watch::Sender<Option<SystemTime>>,
50+
update_buffer: Vec<UpdateEvent>,
51+
last_batch_time: SystemTime,
52+
update_counter: AtomicU64,
4453
}
4554

4655
pub(crate) enum Visibility {
@@ -100,7 +109,70 @@ pub(crate) struct Attribute {
100109
unit: Option<String>,
101110
}
102111

112+
#[derive(Debug)]
113+
pub(crate) enum UpdateEvent {
114+
TaskUpdate(Task),
115+
ResourceUpdate(Resource),
116+
AsyncOpUpdate(AsyncOp),
117+
}
118+
103119
impl State {
120+
pub(crate) fn new() -> (Self, watch::Receiver<Option<SystemTime>>) {
121+
let (tx, rx) = watch::channel(None);
122+
(
123+
Self {
124+
metas: HashMap::new(),
125+
last_updated_at: None,
126+
temporality: Temporality::Live,
127+
tasks_state: TasksState::default(),
128+
resources_state: ResourcesState::default(),
129+
async_ops_state: AsyncOpsState::default(),
130+
current_task_details: Rc::new(RefCell::new(None)),
131+
retain_for: None,
132+
strings: intern::Strings::new(),
133+
last_update: tx,
134+
update_buffer: Vec::with_capacity(UPDATE_BUFFER_SIZE),
135+
last_batch_time: SystemTime::now(),
136+
update_counter: AtomicU64::new(0),
137+
},
138+
rx,
139+
)
140+
}
141+
142+
pub(crate) fn buffer_update(&mut self, event: UpdateEvent) {
143+
self.update_buffer.push(event);
144+
self.update_counter.fetch_add(1, Ordering::SeqCst);
145+
146+
let now = SystemTime::now();
147+
if now.duration_since(self.last_batch_time).unwrap_or(Duration::ZERO) >= UPDATE_BATCH_INTERVAL
148+
|| self.update_buffer.len() >= UPDATE_BUFFER_SIZE
149+
{
150+
self.flush_updates();
151+
}
152+
}
153+
154+
pub(crate) fn flush_updates(&mut self) {
155+
if self.update_buffer.is_empty() {
156+
return;
157+
}
158+
159+
for event in self.update_buffer.drain(..) {
160+
match event {
161+
UpdateEvent::TaskUpdate(task) => self.tasks_state.update_task(task),
162+
UpdateEvent::ResourceUpdate(resource) => self.resources_state.update_resource(resource),
163+
UpdateEvent::AsyncOpUpdate(async_op) => self.async_ops_state.update_async_op(async_op),
164+
}
165+
}
166+
167+
let now = SystemTime::now();
168+
self.last_batch_time = now;
169+
let _ = self.last_update.send(Some(now));
170+
}
171+
172+
pub(crate) fn last_updated_at(&self) -> Option<SystemTime> {
173+
*self.last_update.borrow()
174+
}
175+
104176
pub(crate) fn with_retain_for(mut self, retain_for: Option<Duration>) -> Self {
105177
self.retain_for = retain_for;
106178
self
@@ -114,10 +186,6 @@ impl State {
114186
self
115187
}
116188

117-
pub(crate) fn last_updated_at(&self) -> Option<SystemTime> {
118-
self.last_updated_at
119-
}
120-
121189
pub(crate) fn update(
122190
&mut self,
123191
styles: &view::Styles,

Diff for: tokio-console/src/state/tasks.rs

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub(crate) enum SortBy {
5050
Polls = 8,
5151
Target = 9,
5252
Location = 10,
53+
LastUpdate = 11,
5354
}
5455

5556
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
@@ -621,6 +622,8 @@ impl SortBy {
621622
}
622623
Self::Location => tasks
623624
.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().location.clone())),
625+
Self::LastUpdate => tasks
626+
.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().last_update)),
624627
}
625628
}
626629
}
@@ -646,6 +649,7 @@ impl TryFrom<usize> for SortBy {
646649
idx if idx == Self::Polls as usize => Ok(Self::Polls),
647650
idx if idx == Self::Target as usize => Ok(Self::Target),
648651
idx if idx == Self::Location as usize => Ok(Self::Location),
652+
idx if idx == Self::LastUpdate as usize => Ok(Self::LastUpdate),
649653
_ => Err(()),
650654
}
651655
}

0 commit comments

Comments
 (0)