concurrent reading

This commit is contained in:
Jana Dönszelmann 2026-02-25 15:32:13 +01:00
parent 3d9114dea9
commit 1f6679f57f
No known key found for this signature in database
8 changed files with 104 additions and 84 deletions

View file

@ -1,4 +1,4 @@
use std::rc::Rc; use std::sync::Arc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -6,7 +6,7 @@ use crate::tui::filter::Filter;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Filters { pub struct Filters {
filters: Vec<Rc<Filter>>, filters: Vec<Arc<Filter>>,
undo_pos: usize, undo_pos: usize,
} }
@ -18,11 +18,11 @@ impl Filters {
} }
} }
pub fn get(&self) -> &[Rc<Filter>] { pub fn get(&self) -> &[Arc<Filter>] {
&self.filters[0..self.undo_pos] &self.filters[0..self.undo_pos]
} }
pub fn push(&mut self, filter: Rc<Filter>) { pub fn push(&mut self, filter: Arc<Filter>) {
self.filters.truncate(self.undo_pos); self.filters.truncate(self.undo_pos);
self.filters.push(filter); self.filters.push(filter);
self.undo_pos = self.filters.len(); self.undo_pos = self.filters.len();

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, iter, mem, rc::Rc}; use std::{collections::HashMap, iter, mem, sync::Arc};
use crate::tui::{ use crate::tui::{
filter::Filter, filter::Filter,
@ -58,7 +58,7 @@ impl LogViewer {
pub fn filtered_root_stream(&self) -> Box<dyn LogStream> { pub fn filtered_root_stream(&self) -> Box<dyn LogStream> {
let mut curr = self.root_stream.clone(); let mut curr = self.root_stream.clone();
for filter in self.filters.get() { for filter in self.filters.get() {
curr = Box::new(curr.filter(Rc::clone(filter))); curr = Box::new(curr.filter(Arc::clone(filter)));
} }
curr curr
@ -78,12 +78,12 @@ impl LogViewer {
fn find_elem_in_stream( fn find_elem_in_stream(
stream: &dyn LogStream, stream: &dyn LogStream,
elem: &Rc<LogEntry>, elem: &Arc<LogEntry>,
) -> Option<Box<dyn LogStream>> { ) -> Option<Box<dyn LogStream>> {
let mut temp_stream = stream.clone(); let mut temp_stream = stream.clone();
let mut max = 100usize; let mut max = 100usize;
while let Some((curr, _)) = temp_stream.next() { while let Some((curr, _)) = temp_stream.next() {
if Rc::ptr_eq(&curr, elem) { if Arc::ptr_eq(&curr, elem) {
return Some(temp_stream); return Some(temp_stream);
} }
@ -162,8 +162,8 @@ impl LogViewer {
self.stack = new_stack; self.stack = new_stack;
} }
pub fn add_filter(&mut self, filter: Rc<Filter>) { pub fn add_filter(&mut self, filter: Arc<Filter>) {
self.filters.push(Rc::clone(&filter)); self.filters.push(Arc::clone(&filter));
self.update_filters(); self.update_filters();
} }
@ -200,7 +200,7 @@ impl LogViewer {
} }
} }
pub fn items(&mut self, max: usize) -> Option<(Vec<(Rc<LogEntry>, usize)>, usize)> { pub fn items(&mut self, max: usize) -> Option<(Vec<(Arc<LogEntry>, usize)>, usize)> {
let mut temp_iter = self.curr.iter.clone(); let mut temp_iter = self.curr.iter.clone();
let mut res = Vec::new(); let mut res = Vec::new();
for _ in 0..max { for _ in 0..max {
@ -241,7 +241,7 @@ impl LogViewer {
} }
} }
pub fn selected(&self) -> Option<(Rc<LogEntry>, usize)> { pub fn selected(&self) -> Option<(Arc<LogEntry>, usize)> {
self.curr.selected() self.curr.selected()
} }

View file

@ -1,4 +1,4 @@
use std::rc::Rc; use std::sync::Arc;
use crate::tui::{model::LogEntry, processing::LogStream}; use crate::tui::{model::LogEntry, processing::LogStream};
@ -8,7 +8,7 @@ pub struct LogView {
} }
impl LogView { impl LogView {
pub fn selected(&self) -> Option<(Rc<LogEntry>, usize)> { pub fn selected(&self) -> Option<(Arc<LogEntry>, usize)> {
let mut temp_iter = self.iter.clone(); let mut temp_iter = self.iter.clone();
for _ in 0..self.selection_offset { for _ in 0..self.selection_offset {
let _ = temp_iter.next()?; let _ = temp_iter.next()?;

View file

@ -12,7 +12,7 @@ use std::{
ops::ControlFlow, ops::ControlFlow,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::exit, process::exit,
rc::Rc, sync::Arc,
}; };
use tui_widget_list::{ListBuilder, ListView}; use tui_widget_list::{ListBuilder, ListView};
@ -31,7 +31,7 @@ use ratatui::{
crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
layout::{Constraint, HorizontalAlignment, Layout, Rect}, layout::{Constraint, HorizontalAlignment, Layout, Rect},
prelude::CrosstermBackend, prelude::CrosstermBackend,
style::{Modifier, Style}, style::Style,
text::Line, text::Line,
widgets::{ widgets::{
Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap, Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap,
@ -250,7 +250,7 @@ impl App {
if let InputState::Target(t) = lv.input_state.clone() if let InputState::Target(t) = lv.input_state.clone()
&& let Some(m) = Matcher::from_input(t, lv) && let Some(m) = Matcher::from_input(t, lv)
{ {
lv.add_filter(Rc::new(Filter { lv.add_filter(Arc::new(Filter {
matcher: m, matcher: m,
kind: FilterKind::Remove, kind: FilterKind::Remove,
})); }));
@ -266,7 +266,7 @@ impl App {
if let InputState::Target(t) = lv.input_state.clone() if let InputState::Target(t) = lv.input_state.clone()
&& let Some(m) = Matcher::from_input(t, lv) && let Some(m) = Matcher::from_input(t, lv)
{ {
lv.add_filter(Rc::new(Filter { lv.add_filter(Arc::new(Filter {
matcher: m, matcher: m,
kind: FilterKind::Inline, kind: FilterKind::Inline,
})); }));

View file

@ -2,8 +2,7 @@ use std::{
collections::BTreeMap, collections::BTreeMap,
hash::{DefaultHasher, Hash, Hasher}, hash::{DefaultHasher, Hash, Hasher},
path::PathBuf, path::PathBuf,
rc::Rc, sync::{Arc, OnceLock},
sync::OnceLock,
}; };
use jiff::Timestamp; use jiff::Timestamp;
@ -45,7 +44,7 @@ pub enum LogEntry {
}, },
Sub { Sub {
enter: RawLogEntry, enter: RawLogEntry,
sub_entries: Vec<Rc<LogEntry>>, sub_entries: Vec<Arc<LogEntry>>,
exit: RawLogEntry, exit: RawLogEntry,
count_sub: OnceLock<usize>, count_sub: OnceLock<usize>,

View file

@ -1,4 +1,4 @@
use std::rc::Rc; use std::sync::Arc;
use crate::tui::{ use crate::tui::{
filter::{Filter, FilterKind}, filter::{Filter, FilterKind},
@ -13,23 +13,23 @@ pub trait IntoLogStream {
} }
pub struct LogEntryStream { pub struct LogEntryStream {
inner: Rc<LogEntry>, inner: Arc<LogEntry>,
curr: usize, curr: usize,
inline_depth: usize, inline_depth: usize,
} }
impl LogStream for LogEntryStream { impl LogStream for LogEntryStream {
fn next(&mut self) -> Option<(Rc<LogEntry>, usize)> { fn next(&mut self) -> Option<(Arc<LogEntry>, usize)> {
let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else { let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else {
return None; return None;
}; };
let res = sub_entries.get(self.curr)?; let res = sub_entries.get(self.curr)?;
self.curr += 1; self.curr += 1;
Some((Rc::clone(res), self.inline_depth)) Some((Arc::clone(res), self.inline_depth))
} }
fn prev(&mut self) -> Option<(Rc<LogEntry>, usize)> { fn prev(&mut self) -> Option<(Arc<LogEntry>, usize)> {
let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else { let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else {
return None; return None;
}; };
@ -40,29 +40,29 @@ impl LogStream for LogEntryStream {
self.curr -= 1; self.curr -= 1;
let res = sub_entries.get(self.curr)?; let res = sub_entries.get(self.curr)?;
Some((Rc::clone(res), self.inline_depth)) Some((Arc::clone(res), self.inline_depth))
} }
fn clone(&self) -> Box<dyn LogStream> { fn clone(&self) -> Box<dyn LogStream> {
Box::new(Self { Box::new(Self {
inner: Rc::clone(&self.inner), inner: Arc::clone(&self.inner),
curr: self.curr, curr: self.curr,
inline_depth: self.inline_depth, inline_depth: self.inline_depth,
}) })
} }
fn enclosing_log_entry(&self) -> Option<(Rc<LogEntry>, usize)> { fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)> {
Some((Rc::clone(&self.inner), self.inline_depth)) Some((Arc::clone(&self.inner), self.inline_depth))
} }
} }
impl IntoLogStream for &Rc<LogEntry> { impl IntoLogStream for &Arc<LogEntry> {
type Stream = LogEntryStream; type Stream = LogEntryStream;
fn from_end(self, inline_depth: usize) -> Option<Self::Stream> { fn from_end(self, inline_depth: usize) -> Option<Self::Stream> {
if let LogEntry::Sub { sub_entries, .. } = self.as_ref() { if let LogEntry::Sub { sub_entries, .. } = self.as_ref() {
Some(LogEntryStream { Some(LogEntryStream {
inner: Rc::clone(&self), inner: Arc::clone(&self),
curr: sub_entries.len(), curr: sub_entries.len(),
inline_depth, inline_depth,
}) })
@ -74,7 +74,7 @@ impl IntoLogStream for &Rc<LogEntry> {
fn from_start(self, inline_depth: usize) -> Option<Self::Stream> { fn from_start(self, inline_depth: usize) -> Option<Self::Stream> {
if let LogEntry::Sub { .. } = self.as_ref() { if let LogEntry::Sub { .. } = self.as_ref() {
Some(LogEntryStream { Some(LogEntryStream {
inner: Rc::clone(&self), inner: Arc::clone(&self),
curr: 0, curr: 0,
inline_depth, inline_depth,
}) })
@ -85,14 +85,14 @@ impl IntoLogStream for &Rc<LogEntry> {
} }
pub trait LogStream { pub trait LogStream {
fn next(&mut self) -> Option<(Rc<LogEntry>, usize)>; fn next(&mut self) -> Option<(Arc<LogEntry>, usize)>;
fn prev(&mut self) -> Option<(Rc<LogEntry>, usize)>; fn prev(&mut self) -> Option<(Arc<LogEntry>, usize)>;
fn enclosing_log_entry(&self) -> Option<(Rc<LogEntry>, usize)>; fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)>;
fn clone(&self) -> Box<dyn LogStream>; fn clone(&self) -> Box<dyn LogStream>;
fn filter(&self, filter: Rc<Filter>) -> FilteredLogStream { fn filter(&self, filter: Arc<Filter>) -> FilteredLogStream {
FilteredLogStream { FilteredLogStream {
filter: filter, filter: filter,
stack: vec![(self.enclosing_log_entry(), self.clone())], stack: vec![(self.enclosing_log_entry(), self.clone())],
@ -101,8 +101,8 @@ pub trait LogStream {
} }
pub struct FilteredLogStream { pub struct FilteredLogStream {
filter: Rc<Filter>, filter: Arc<Filter>,
stack: Vec<(Option<(Rc<LogEntry>, usize)>, Box<dyn LogStream>)>, stack: Vec<(Option<(Arc<LogEntry>, usize)>, Box<dyn LogStream>)>,
} }
macro_rules! generate_candidate { macro_rules! generate_candidate {
@ -140,7 +140,7 @@ macro_rules! generate_filter {
if let Some(iter) = elem.$into_iter(inline_depth + 1) { if let Some(iter) = elem.$into_iter(inline_depth + 1) {
$_self $_self
.stack .stack
.push((Some((Rc::clone(&elem), inline_depth)), Box::new(iter))); .push((Some((Arc::clone(&elem), inline_depth)), Box::new(iter)));
} }
// Continue so we actually return a nested item. // Continue so we actually return a nested item.
if $forwards { if $forwards {
@ -162,26 +162,26 @@ macro_rules! generate_filter {
} }
impl FilteredLogStream { impl FilteredLogStream {
fn next_candidate(&mut self) -> Option<(Rc<LogEntry>, usize)> { fn next_candidate(&mut self) -> Option<(Arc<LogEntry>, usize)> {
generate_candidate!(self, next, true) generate_candidate!(self, next, true)
} }
fn prev_candidate(&mut self) -> Option<(Rc<LogEntry>, usize)> { fn prev_candidate(&mut self) -> Option<(Arc<LogEntry>, usize)> {
generate_candidate!(self, prev, false) generate_candidate!(self, prev, false)
} }
} }
impl LogStream for FilteredLogStream { impl LogStream for FilteredLogStream {
fn next(&mut self) -> Option<(Rc<LogEntry>, usize)> { fn next(&mut self) -> Option<(Arc<LogEntry>, usize)> {
generate_filter!(self, next_candidate, from_start, true) generate_filter!(self, next_candidate, from_start, true)
} }
fn prev(&mut self) -> Option<(Rc<LogEntry>, usize)> { fn prev(&mut self) -> Option<(Arc<LogEntry>, usize)> {
generate_filter!(self, prev_candidate, from_end, false) generate_filter!(self, prev_candidate, from_end, false)
} }
fn clone(&self) -> Box<dyn LogStream> { fn clone(&self) -> Box<dyn LogStream> {
Box::new(Self { Box::new(Self {
filter: Rc::clone(&self.filter), filter: Arc::clone(&self.filter),
stack: self stack: self
.stack .stack
.iter() .iter()
@ -190,7 +190,7 @@ impl LogStream for FilteredLogStream {
}) })
} }
fn enclosing_log_entry(&self) -> Option<(Rc<LogEntry>, usize)> { fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)> {
self.stack[0].0.clone() self.stack[0].0.clone()
} }
} }

View file

@ -4,8 +4,12 @@ use std::{
io::{self, BufRead, BufReader}, io::{self, BufRead, BufReader},
mem, mem,
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, sync::Arc,
sync::OnceLock, sync::{
OnceLock,
mpsc::{Receiver, sync_channel},
},
thread,
}; };
use crate::tui::{ use crate::tui::{
@ -13,32 +17,14 @@ use crate::tui::{
processing::LogStream, processing::LogStream,
}; };
struct Inner { struct LogFileEntryGenerator {
pub path: PathBuf,
file: BufReader<File>, file: BufReader<File>,
cached_entries: Vec<Rc<LogEntry>>,
} }
#[derive(Clone)] impl LogFileEntryGenerator {
pub struct LogfileReader(Rc<RefCell<Inner>>);
impl LogfileReader {
pub fn new(p: &Path) -> io::Result<Self> {
Ok(Self(Rc::new(RefCell::new(Inner {
file: BufReader::new(File::open(p)?),
path: p.to_path_buf(),
cached_entries: Vec::new(),
}))))
}
pub fn path(&self) -> PathBuf {
self.0.borrow().path.clone()
}
fn next_line(&mut self) -> Option<String> { fn next_line(&mut self) -> Option<String> {
let mut res = String::new(); let mut res = String::new();
match self.0.borrow_mut().file.read_line(&mut res) { match self.file.read_line(&mut res) {
Err(e) => { Err(e) => {
eprintln!("error: {e:?}"); eprintln!("error: {e:?}");
None None
@ -59,14 +45,14 @@ impl LogfileReader {
} }
} }
fn next_entry(&mut self) -> Option<Rc<LogEntry>> { fn next_entry(&mut self) -> Option<Arc<LogEntry>> {
let mut stack = Vec::<(RawLogEntry, Vec<Rc<LogEntry>>)>::new(); let mut stack = Vec::<(RawLogEntry, Vec<Arc<LogEntry>>)>::new();
let mut curr = Vec::<Rc<LogEntry>>::new(); let mut curr = Vec::<Arc<LogEntry>>::new();
loop { loop {
let entry = self.next_raw_entry()?; let entry = self.next_raw_entry()?;
let new_entry = Rc::new(match entry.fields.message() { let new_entry = Arc::new(match entry.fields.message() {
Some("enter") => { Some("enter") => {
stack.push((entry, mem::take(&mut curr))); stack.push((entry, mem::take(&mut curr)));
continue; continue;
@ -94,14 +80,49 @@ impl LogfileReader {
} }
} }
} }
}
fn fill_buf_to_access_index(&mut self, n: usize) -> Option<Rc<LogEntry>> { struct Inner {
pub path: PathBuf,
entry_generator: Receiver<Arc<LogEntry>>,
cached_entries: Vec<Arc<LogEntry>>,
}
#[derive(Clone)]
pub struct LogfileReader(Arc<RefCell<Inner>>);
impl LogfileReader {
pub fn new(p: &Path) -> io::Result<Self> {
let (tx, rx) = sync_channel(1000);
let mut generator = LogFileEntryGenerator {
file: BufReader::new(File::open(p)?),
};
thread::spawn(move || {
while let Some(i) = generator.next_entry() {
tx.send(i).unwrap();
}
});
Ok(Self(Arc::new(RefCell::new(Inner {
path: p.to_path_buf(),
cached_entries: Vec::new(),
entry_generator: rx,
}))))
}
pub fn path(&self) -> PathBuf {
self.0.borrow().path.clone()
}
fn fill_buf_to_access_index(&mut self, n: usize) -> Option<Arc<LogEntry>> {
while self.0.borrow().cached_entries.len() <= n { while self.0.borrow().cached_entries.len() <= n {
let entry = self.next_entry()?; let entry = self.0.borrow().entry_generator.recv().ok()?;
self.0.borrow_mut().cached_entries.push(entry); self.0.borrow_mut().cached_entries.push(entry);
} }
Some(Rc::clone(&self.0.borrow().cached_entries[n])) Some(Arc::clone(&self.0.borrow().cached_entries[n]))
} }
pub fn iter(&self) -> LogFileReaderStream { pub fn iter(&self) -> LogFileReaderStream {
@ -118,14 +139,14 @@ pub struct LogFileReaderStream {
} }
impl LogStream for LogFileReaderStream { impl LogStream for LogFileReaderStream {
fn next(&mut self) -> Option<(Rc<LogEntry>, usize)> { fn next(&mut self) -> Option<(Arc<LogEntry>, usize)> {
let entry = self.reader.fill_buf_to_access_index(self.curr)?; let entry = self.reader.fill_buf_to_access_index(self.curr)?;
self.curr += 1; self.curr += 1;
Some((entry, 0)) Some((entry, 0))
} }
fn prev(&mut self) -> Option<(Rc<LogEntry>, usize)> { fn prev(&mut self) -> Option<(Arc<LogEntry>, usize)> {
if self.curr == 0 { if self.curr == 0 {
return None; return None;
} }
@ -143,7 +164,7 @@ impl LogStream for LogFileReaderStream {
}) })
} }
fn enclosing_log_entry(&self) -> Option<(Rc<LogEntry>, usize)> { fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)> {
None None
} }
} }

View file

@ -1,4 +1,4 @@
use std::{cell::OnceCell, rc::Rc}; use std::{cell::OnceCell, sync::Arc};
use itertools::Itertools; use itertools::Itertools;
use ratatui::widgets::{List, ListItem, Widget}; use ratatui::widgets::{List, ListItem, Widget};
@ -19,7 +19,7 @@ pub struct TreeState {
} }
impl TreeState { impl TreeState {
pub fn from_items(items: &[(Rc<LogEntry>, usize)]) -> Self { pub fn from_items(items: &[(Arc<LogEntry>, usize)]) -> Self {
let mut res = Vec::new(); let mut res = Vec::new();
let mut curr = String::new(); let mut curr = String::new();
let mut prev_depth = 0; let mut prev_depth = 0;
@ -88,7 +88,7 @@ impl TreeState {
} }
pub struct Items<'a> { pub struct Items<'a> {
items: Vec<(Rc<LogEntry>, usize)>, items: Vec<(Arc<LogEntry>, usize)>,
selected_offset: usize, selected_offset: usize,
input_state: &'a InputState, input_state: &'a InputState,
@ -98,7 +98,7 @@ pub struct Items<'a> {
impl<'a> Items<'a> { impl<'a> Items<'a> {
pub fn new( pub fn new(
items: Vec<(Rc<LogEntry>, usize)>, items: Vec<(Arc<LogEntry>, usize)>,
selected_offset: usize, selected_offset: usize,
input_state: &'a InputState, input_state: &'a InputState,
selected_footer_field: Option<(String, String)>, selected_footer_field: Option<(String, String)>,
@ -113,7 +113,7 @@ impl<'a> Items<'a> {
} }
} }
pub fn selected(&self) -> Option<&Rc<LogEntry>> { pub fn selected(&self) -> Option<&Arc<LogEntry>> {
self.items.get(self.selected_offset).map(|(s, _)| s) self.items.get(self.selected_offset).map(|(s, _)| s)
} }
} }