cache sparklines

This commit is contained in:
Robin Appelman 2024-07-29 20:45:02 +02:00
commit 5b1ae14639
7 changed files with 46 additions and 41 deletions

View file

@ -37,6 +37,7 @@ pub struct LogMatch {
pub result: Option<MatchResult>, pub result: Option<MatchResult>,
pub lines: Vec<usize>, pub lines: Vec<usize>,
pub histogram: TimeGraph, pub histogram: TimeGraph,
pub sparkline: String,
pub grouped: Vec<GroupedLines>, pub grouped: Vec<GroupedLines>,
} }
@ -49,11 +50,13 @@ impl LogMatch {
histogram.add(line.time); histogram.add(line.time);
} }
let grouped = group_lines(all_lines, lines.iter().copied()); let grouped = group_lines(all_lines, lines.iter().copied());
let sparkline = histogram.sparkline::<10>();
LogMatch { LogMatch {
result, result,
lines, lines,
histogram, histogram,
sparkline,
grouped, grouped,
} }
} }
@ -84,6 +87,7 @@ fn group_lines<I: Iterator<Item = usize>>(all_lines: &[LogLine], indices: I) ->
pub struct GroupedLines { pub struct GroupedLines {
pub lines: Vec<usize>, pub lines: Vec<usize>,
pub histogram: TimeGraph, pub histogram: TimeGraph,
pub sparkline: String,
} }
impl GroupedLines { impl GroupedLines {
@ -94,7 +98,12 @@ impl GroupedLines {
for line in lines.iter().map(|line| &all_lines[*line]) { for line in lines.iter().map(|line| &all_lines[*line]) {
histogram.add(line.time); histogram.add(line.time);
} }
GroupedLines { lines, histogram } let sparkline = histogram.sparkline::<10>();
GroupedLines {
lines,
histogram,
sparkline,
}
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {

View file

@ -28,12 +28,30 @@ impl TimeGraph {
.unwrap() .unwrap()
} }
pub fn counts(&self, buckets: usize) -> Vec<u64> { pub fn counts(&self, buckets: usize) -> impl Iterator<Item = u64> + '_ {
let step = (self.end - self.start + 1) / buckets as u64; let step = (self.end - self.start + 1) / buckets as u64;
self.histogram self.histogram
.iter_linear(step) .iter_linear(step)
.map(|val| val.count_since_last_iteration()) .map(|val| val.count_since_last_iteration())
}
pub fn sparkline<const N: usize>(&self) -> String {
let mut values = [0; N];
for (value, count) in values.iter_mut().zip(self.counts(N)) {
*value = count;
}
let max = values.iter().copied().max().unwrap() as f64;
let len = SPARKS.len() as f64 - 1.0;
values
.iter()
.copied()
.map(|val| {
let rel = val as f64 / max;
SPARKS[(rel * len) as usize]
})
.collect() .collect()
} }
} }
const SPARKS: &[char] = &[' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];

View file

@ -17,28 +17,8 @@ impl Widget for UiHistogram<'_> {
where where
Self: Sized, Self: Sized,
{ {
let values = self.data.counts(area.width as usize); let values: Vec<_> = self.data.counts(area.width as usize).collect();
let sparkline = Sparkline::default().data(&values); let sparkline = Sparkline::default().data(&values);
sparkline.render(area, buf) sparkline.render(area, buf)
} }
} }
const SPARKS: &[char] = &[' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
pub fn sparkline(values: &[u64]) -> String {
let max = values.iter().copied().max().unwrap() as f64;
let len = SPARKS.len() as f64 - 1.0;
values
.iter()
.copied()
.map(|val| {
let rel = val as f64 / max;
SPARKS[(rel * len) as usize]
})
.collect()
}
#[test]
fn test_sparkline() {
assert_eq!(" ▇█", sparkline(&[0, 900, 1000]));
}

View file

@ -1,5 +1,4 @@
use crate::app::{App, LogMatch}; use crate::app::{App, LogMatch};
use crate::ui::histogram::sparkline;
use crate::ui::style::TABLE_HEADER_STYLE; use crate::ui::style::TABLE_HEADER_STYLE;
use crate::ui::table::ScrollbarTable; use crate::ui::table::ScrollbarTable;
use itertools::Either; use itertools::Either;
@ -46,7 +45,7 @@ pub fn match_list(app: &App) -> ScrollbarTable {
.header(header) .header(header)
} }
fn log_row<'a>(result: &LogMatch, app: &'a App, name: &'static str) -> Row<'a> { fn log_row<'a>(result: &'a LogMatch, app: &'a App, name: &'static str) -> Row<'a> {
if let Some(match_result) = &result.result { if let Some(match_result) = &result.result {
let mut message = String::new(); let mut message = String::new();
let mut paths = String::new(); let mut paths = String::new();
@ -61,7 +60,7 @@ fn log_row<'a>(result: &LogMatch, app: &'a App, name: &'static str) -> Row<'a> {
Text::from(message), Text::from(message),
Text::from(paths), Text::from(paths),
Text::from(lines).alignment(Alignment::Right), Text::from(lines).alignment(Alignment::Right),
Text::from(sparkline(&result.histogram.counts(10))), Text::from(result.sparkline.as_str()),
Text::from(result.count().to_string()), Text::from(result.count().to_string()),
]) ])
.height(match_result.len() as u16) .height(match_result.len() as u16)
@ -70,7 +69,7 @@ fn log_row<'a>(result: &LogMatch, app: &'a App, name: &'static str) -> Row<'a> {
Text::from(name), Text::from(name),
Text::from(""), Text::from(""),
Text::from(""), Text::from(""),
Text::from(sparkline(&result.histogram.counts(10))), Text::from(result.sparkline.as_str()),
Text::from(result.count().to_string()), Text::from(result.count().to_string()),
]) ])
} }

View file

@ -7,7 +7,7 @@ use ratatui::text::Text;
use ratatui::widgets::{Cell, Row}; use ratatui::widgets::{Cell, Row};
use time::format_description::well_known::Iso8601; use time::format_description::well_known::Iso8601;
pub fn raw_logs(app: &App, lines: &[usize]) -> ScrollbarTable<'static> { pub fn raw_logs<'a>(app: &'a App, lines: &[usize]) -> ScrollbarTable<'a> {
let lines = lines.iter().copied().map(|i| &app.lines[i]); let lines = lines.iter().copied().map(|i| &app.lines[i]);
let header = [ let header = [
Text::from("Level"), Text::from("Level"),
@ -30,10 +30,10 @@ pub fn raw_logs(app: &App, lines: &[usize]) -> ScrollbarTable<'static> {
ScrollbarTable::new(lines.map(log_row), widths).header(header) ScrollbarTable::new(lines.map(log_row), widths).header(header)
} }
fn log_row(line: &LogLine) -> Row<'static> { fn log_row(line: &LogLine) -> Row {
Row::new([ Row::new([
Text::from(line.level.as_str().to_string()), Text::from(line.level.as_str()),
Text::from(line.app.to_string()), Text::from(line.app.as_str()),
Text::from(line.display()), Text::from(line.display()),
Text::from(line.time.format(&Iso8601::<TIME_FORMAT>).unwrap()).alignment(Alignment::Right), Text::from(line.time.format(&Iso8601::<TIME_FORMAT>).unwrap()).alignment(Alignment::Right),
]) ])

View file

@ -74,7 +74,7 @@ fn exception_trace(exception: &FullException) -> impl Iterator<Item = Row> + '_
let exception_row = Row::new([ let exception_row = Row::new([
Text::from(""), Text::from(""),
Text::from(exception.line.to_string()).alignment(Alignment::Right), Text::from(exception.line.to_string()).alignment(Alignment::Right),
Text::from(exception.file.clone()), Text::from(exception.file.as_str()),
]) ])
.style(TABLE_HEADER_STYLE); .style(TABLE_HEADER_STYLE);
let trace_rows = exception.trace.iter().map(trace_line); let trace_rows = exception.trace.iter().map(trace_line);
@ -83,7 +83,7 @@ fn exception_trace(exception: &FullException) -> impl Iterator<Item = Row> + '_
fn trace_line(trace: &Trace) -> Row { fn trace_line(trace: &Trace) -> Row {
Row::new([ Row::new([
Text::from(trace.file.clone()), Text::from(trace.file.as_str()),
Text::from(if trace.line > 0 { Text::from(if trace.line > 0 {
trace.line.to_string() trace.line.to_string()
} else { } else {

View file

@ -1,12 +1,11 @@
use crate::app::{App, GroupedLines, LogMatch}; use crate::app::{App, GroupedLines, LogMatch};
use crate::ui::histogram::sparkline;
use crate::ui::style::TABLE_HEADER_STYLE; use crate::ui::style::TABLE_HEADER_STYLE;
use crate::ui::table::ScrollbarTable; use crate::ui::table::ScrollbarTable;
use ratatui::layout::Constraint; use ratatui::layout::Constraint;
use ratatui::text::Text; use ratatui::text::Text;
use ratatui::widgets::{Cell, Row}; use ratatui::widgets::{Cell, Row};
pub fn grouped_lines(app: &App, log_match: &LogMatch) -> ScrollbarTable<'static> { pub fn grouped_lines<'a>(app: &'a App, log_match: &'a LogMatch) -> ScrollbarTable<'a> {
let grouped = &log_match.grouped; let grouped = &log_match.grouped;
let header = [ let header = [
Text::from("Level"), Text::from("Level"),
@ -31,14 +30,14 @@ pub fn grouped_lines(app: &App, log_match: &LogMatch) -> ScrollbarTable<'static>
ScrollbarTable::new(grouped.iter().map(|group| group_row(app, group)), widths).header(header) ScrollbarTable::new(grouped.iter().map(|group| group_row(app, group)), widths).header(header)
} }
fn group_row(app: &App, group: &GroupedLines) -> Row<'static> { fn group_row<'a>(app: &'a App, group: &'a GroupedLines) -> Row<'a> {
let line = &app.lines[group.lines[0]]; let line = &app.lines[group.lines[0]];
Row::new([ Row::new([
line.level.as_str().to_string(), Text::from(line.level.as_str()),
line.app.to_string(), Text::from(line.app.as_str()),
line.display(), Text::from(line.display()),
sparkline(&group.histogram.counts(10)), Text::from(group.sparkline.as_str()),
group.len().to_string(), Text::from(group.len().to_string()),
]) ])
} }