refactor: reduce lines of log cloned

Instead of cloning every single line of logs, now we only clone those logs that are visible +- a padding.

Containers with hunders or thousands of lines of logs can see a huge reducing in CPU and memory usage
This commit is contained in:
Jack Wills
2025-06-16 20:54:40 +00:00
parent e7114d2f5e
commit ecefa302b9
3 changed files with 82 additions and 7 deletions
+18 -3
View File
@@ -586,10 +586,25 @@ impl Logs {
}
}
pub fn to_vec(&self) -> Vec<ListItem<'static>> {
self.logs.items.clone()
/// Get the logs vec, but instead of cloning to whole vec, only clone items with x of the currently selected index
/// Where x is the abs different of the index plus the panel height & a padding
/// The rest can be just empty list items
/// TODO test me, pass in 1000 lines of "something", expect response to be different!
pub fn to_vec(&self, height: usize, padding: usize) -> Vec<ListItem<'static>> {
let current_index = self.logs.state.selected().unwrap_or_default();
self.logs
.items
.iter()
.enumerate()
.map(|(index, item)| {
if current_index.abs_diff(index) <= height + padding {
item.clone()
} else {
ListItem::from("")
}
})
.collect()
}
/// The rest of the methods are basically forwarding from the underlying StatefulList
pub fn get_state_title(&self) -> String {
self.logs.get_state_title()
+61 -3
View File
@@ -677,12 +677,12 @@ impl AppData {
}
/// Get mutable Vec of current containers logs
pub fn get_logs(&self) -> Vec<ListItem<'static>> {
pub fn get_logs(&self, height: u16, padding: usize) -> Vec<ListItem<'static>> {
self.containers
.state
.selected()
.and_then(|i| self.containers.items.get(i))
.map_or(vec![], |i| i.logs.to_vec())
.map_or(vec![], |i| i.logs.to_vec(height.into(), padding))
}
/// Get mutable Option of the currently selected container Logs state
@@ -1953,7 +1953,7 @@ mod tests {
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
assert_eq!(result.unwrap().offset(), 0);
let result = app_data.get_logs();
let result = app_data.get_logs(4, 1);
assert_eq!(result.len(), 3);
let result = app_data.get_log_title();
@@ -2324,4 +2324,62 @@ mod tests {
let result = app_data.get_log_state();
assert!(result.is_none());
}
// *************** //
// Get logs method //
// *************** //
#[test]
/// get_logs() returns vec of item, but the items are empty unless they are in the *visible" zone, based on height, index, and padding
fn test_app_data_update_get_logs() {
let (ids, containers) = gen_containers();
let mut app_data = gen_appdata(&containers);
app_data.containers_start();
let logs = (0..=999).map(|i| format!("{i} {i}")).collect::<Vec<_>>();
app_data.update_log_by_id(logs, &ids[0]);
let result = app_data.get_logs(10, 10);
for (index, item) in result.iter().enumerate() {
if index < 979 {
assert_eq!(item, &ListItem::new(""));
} else {
assert_eq!(item, &ListItem::new(format!("{index}")));
}
}
let result = app_data.get_logs(100, 20);
for (index, item) in result.iter().enumerate() {
if index < 879 {
assert_eq!(item, &ListItem::new(""));
} else {
assert_eq!(item, &ListItem::new(format!("{index}")));
}
}
app_data.log_start();
let result = app_data.get_logs(10, 10);
for (index, item) in result.iter().enumerate() {
if index > 20 {
assert_eq!(item, &ListItem::new(""));
} else {
assert_eq!(item, &ListItem::new(format!("{index}")));
}
}
for _ in 0..=500 {
app_data.log_next();
}
let result = app_data.get_logs(10, 10);
for (index, item) in result.iter().enumerate() {
if (481..=521).contains(&index) {
assert_eq!(item, &ListItem::new(format!("{index}")));
} else {
assert_eq!(item, &ListItem::new(""));
}
}
}
}