Skip to content

Commit

Permalink
Merge pull request #30 from Netflix/fix-sorted-selected
Browse files Browse the repository at this point in the history
Correctly display graphs when sorting is enabled
  • Loading branch information
jfernandez authored Apr 7, 2024
2 parents f162295 + c6a6563 commit 152eb66
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 57 deletions.
79 changes: 37 additions & 42 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::bpf_program::BpfProgram;

pub struct App {
pub mode: Mode,
pub state: Arc<Mutex<TableState>>,
pub table_state: TableState,
pub header_columns: [String; 7],
pub items: Arc<Mutex<Vec<BpfProgram>>>,
pub data_buf: Arc<Mutex<CircularBuffer<20, PeriodMeasure>>>,
Expand All @@ -40,6 +40,7 @@ pub struct App {
pub max_runtime: u64,
pub filter_input: Arc<Mutex<Input>>,
pub selected_column: Option<usize>,
pub graphs_bpf_program: Arc<Mutex<Option<BpfProgram>>>,
sorted_column: Arc<Mutex<SortColumn>>,
}

Expand Down Expand Up @@ -68,7 +69,7 @@ impl App {
pub fn new() -> App {
App {
mode: Mode::Table,
state: Arc::new(Mutex::new(TableState::default())),
table_state: TableState::default(),
header_columns: [
String::from("ID "),
String::from("Type "),
Expand All @@ -85,16 +86,17 @@ impl App {
max_runtime: 0,
filter_input: Arc::new(Mutex::new(Input::default())),
selected_column: None,
graphs_bpf_program: Arc::new(Mutex::new(None)),
sorted_column: Arc::new(Mutex::new(SortColumn::NoOrder)),
}
}

pub fn start_background_thread(&self) {
let items = Arc::clone(&self.items);
let data_buf = Arc::clone(&self.data_buf);
let state = Arc::clone(&self.state);
let filter = Arc::clone(&self.filter_input);
let sort_col = Arc::clone(&self.sorted_column);
let graphs_bpf_program = Arc::clone(&self.graphs_bpf_program);

thread::spawn(move || loop {
let loop_start = Instant::now();
Expand Down Expand Up @@ -147,31 +149,20 @@ impl App {
bpf_program.period_ns = prev_bpf_program.instant.elapsed().as_nanos();
}

items.push(bpf_program);
}

let mut state = state.lock().unwrap();
let mut data_buf = data_buf.lock().unwrap();
if let Some(index) = state.selected() {
// If the selected index is out of bounds, unselect it.
// This can happen if a program exits while it's selected.
if index >= items.len() {
state.select(None);
continue;
if let Some(graphs_bpf_program) = graphs_bpf_program.lock().unwrap().as_ref() {
if bpf_program.id == graphs_bpf_program.id {
let mut data_buf = data_buf.lock().unwrap();
data_buf.push_back(PeriodMeasure {
cpu_time_percent: bpf_program.cpu_time_percent(),
events_per_sec: bpf_program.events_per_second(),
average_runtime_ns: bpf_program.period_average_runtime_ns(),
});
}
}

let bpf_program = &items[index];
data_buf.push_back(PeriodMeasure {
cpu_time_percent: bpf_program.cpu_time_percent(),
events_per_sec: bpf_program.events_per_second(),
average_runtime_ns: bpf_program.period_average_runtime_ns(),
});
items.push(bpf_program);
}

// Explicitly drop the MutexGuards to unlock before sleeping.
drop(data_buf);
drop(state);

// Sort items based on index of the column
let sort_col = sort_col.lock().unwrap();
match *sort_col {
Expand Down Expand Up @@ -219,29 +210,34 @@ impl App {
});
}

pub fn toggle_graphs(&mut self) {
pub fn show_graphs(&mut self) {
self.data_buf.lock().unwrap().clear();
self.max_cpu = 0.0;
self.max_eps = 0;
self.max_runtime = 0;
self.mode = match &self.mode {
Mode::Table => Mode::Graph,
_ => Mode::Table,
}
self.mode = Mode::Graph;
*self.graphs_bpf_program.lock().unwrap() = self.selected_program().map(|prog| prog.clone());
}

pub fn show_table(&mut self) {
self.mode = Mode::Table;
self.data_buf.lock().unwrap().clear();
self.max_cpu = 0.0;
self.max_eps = 0;
self.max_runtime = 0;
*self.graphs_bpf_program.lock().unwrap() = None;
}

pub fn selected_program(&self) -> Option<BpfProgram> {
let items = self.items.lock().unwrap();
let state = self.state.lock().unwrap();

state.selected().map(|i| items[i].clone())

self.table_state.selected().and_then(|i| items.get(i).cloned())
}

pub fn next_program(&mut self) {
let items = self.items.lock().unwrap();
if items.len() > 0 {
let mut state = self.state.lock().unwrap();
let i = match state.selected() {
let i = match self.table_state.selected() {
Some(i) => {
if i >= items.len() - 1 {
0
Expand All @@ -251,15 +247,14 @@ impl App {
}
None => 0,
};
state.select(Some(i));
self.table_state.select(Some(i));
}
}

pub fn previous_program(&mut self) {
let items = self.items.lock().unwrap();
if items.len() > 0 {
let mut state = self.state.lock().unwrap();
let i = match state.selected() {
let i = match self.table_state.selected() {
Some(i) => {
if i == 0 {
items.len() - 1
Expand All @@ -269,7 +264,7 @@ impl App {
}
None => items.len() - 1,
};
state.select(Some(i));
self.table_state.select(Some(i));
}
}

Expand Down Expand Up @@ -495,8 +490,8 @@ mod tests {
// Initially, UI should be in table mode
assert_eq!(app.mode, Mode::Table);

// After calling toggle_graphs, UI should be in graph mode
app.toggle_graphs();
// After calling show_graphs, UI should be in graph mode
app.show_graphs();
assert_eq!(app.mode, Mode::Graph);

// Set max_cpu, max_eps, and max_runtime to non-zero values
Expand All @@ -509,8 +504,8 @@ mod tests {
average_runtime_ns: 100,
});

// After calling toggle_graphs, UI should be in table mode again
app.toggle_graphs();
// After calling show_table, UI should be in table mode again
app.show_table();
assert_eq!(app.mode, Mode::Table);

// max_cpu, max_eps, and max_runtime should be reset to 0
Expand Down
30 changes: 15 additions & 15 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::fs;
/**
*
* Copyright 2024 Netflix, Inc.
Expand All @@ -16,7 +17,6 @@
*
*/
use std::io;
use std::fs;
use std::os::fd::{FromRawFd, OwnedFd};
use std::time::Duration;

Expand Down Expand Up @@ -142,14 +142,14 @@ fn run_draw_loop<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> Result
Mode::Table => match key.code {
KeyCode::Down | KeyCode::Char('j') => app.next_program(),
KeyCode::Up | KeyCode::Char('k') => app.previous_program(),
KeyCode::Enter => app.toggle_graphs(),
KeyCode::Enter => app.show_graphs(),
KeyCode::Char('f') => app.toggle_filter(),
KeyCode::Char('s') => app.toggle_sort(),
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
_ => {}
},
Mode::Graph => match key.code {
KeyCode::Enter | KeyCode::Esc => app.toggle_graphs(),
KeyCode::Enter | KeyCode::Esc => app.show_table(),
KeyCode::Char('q') => return Ok(()),
_ => {}
},
Expand Down Expand Up @@ -188,12 +188,6 @@ fn run_draw_loop<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> Result
fn ui(f: &mut Frame, app: &mut App) {
let rects = Layout::vertical([Constraint::Min(5), Constraint::Length(3)]).split(f.size());

// This can happen when the program exists while the user is viewing the graphs.
// In this case, we want to switch back to the table view.
if app.selected_program().is_none() && app.mode == Mode::Graph {
app.mode = Mode::Table;
}

match app.mode {
Mode::Table | Mode::Filter | Mode::Sort => render_table(f, app, rects[0]),
Mode::Graph => render_graphs(f, app, rects[0]),
Expand Down Expand Up @@ -249,9 +243,15 @@ fn render_graphs(f: &mut Frame, app: &mut App, area: Rect) {
let max_cpu = moving_max_cpu;
let max_eps = moving_max_eps as f64;
let max_runtime = moving_max_runtime as f64;
let avg_cpu = total_cpu / data_buf.len() as f64;
let avg_eps = total_eps as f64 / data_buf.len() as f64;
let avg_runtime = total_runtime as f64 / data_buf.len() as f64;

let mut avg_cpu = 0.0;
let mut avg_eps = 0.0;
let mut avg_runtime = 0.0;
if data_buf.len() > 0 {
avg_cpu = total_cpu / data_buf.len() as f64;
avg_eps = total_eps as f64 / data_buf.len() as f64;
avg_runtime = total_runtime as f64 / data_buf.len() as f64;
}

let cpu_y_max = app.max_cpu.ceil();
let eps_y_max = (app.max_eps as f64 * 2.0).ceil();
Expand Down Expand Up @@ -371,8 +371,8 @@ fn render_graphs(f: &mut Frame, app: &mut App, area: Rect) {
Row::new(vec![Cell::from("Program Name"), Cell::from("Unknown")]),
];
let widths = [Constraint::Length(15), Constraint::Min(0)];

if let Some(bpf_program) = app.selected_program() {
if let Some(bpf_program) = app.graphs_bpf_program.lock().unwrap().clone() {
items = vec![
Row::new(vec![
Cell::from("Program ID".bold()),
Expand Down Expand Up @@ -453,7 +453,7 @@ fn render_table(f: &mut Frame, app: &mut App, area: Rect) {
)
.highlight_style(selected_style)
.highlight_symbol(">> ");
f.render_stateful_widget(t, area, &mut app.state.lock().unwrap());
f.render_stateful_widget(t, area, &mut app.table_state);
}

fn render_footer(f: &mut Frame, app: &mut App, area: Rect) {
Expand Down

0 comments on commit 152eb66

Please sign in to comment.