stack sparkline

This commit is contained in:
Robin Appelman 2025-08-09 17:27:25 +02:00
commit 36bdc23080
3 changed files with 38 additions and 16 deletions

View file

@ -2,7 +2,7 @@
name = "logsmash" name = "logsmash"
version = "0.1.11" version = "0.1.11"
edition = "2021" edition = "2021"
rust-version = "1.85.0" rust-version = "1.87.0"
license = "GPL-3.0-only" license = "GPL-3.0-only"
[dependencies] [dependencies]

View file

@ -2,7 +2,7 @@ use crate::grouping::group_lines_by;
use crate::logfile::{LogFile, LogLine, LogLineNumber}; use crate::logfile::{LogFile, LogLine, LogLineNumber};
use crate::logs::ParsedLogs; use crate::logs::ParsedLogs;
use crate::matcher::MatchResult; use crate::matcher::MatchResult;
use crate::timegraph::TimeGraph; use crate::timegraph::{SparkLine, TimeGraph};
use logsmash_data::{LoggingStatementWithPathPrefix, StatementList}; use logsmash_data::{LoggingStatementWithPathPrefix, StatementList};
use regex::{escape, Regex, RegexBuilder}; use regex::{escape, Regex, RegexBuilder};
use serde_json::Error as JsonError; use serde_json::Error as JsonError;
@ -56,7 +56,7 @@ pub struct LogMatch<'logs> {
pub result: Option<MatchResult>, pub result: Option<MatchResult>,
pub count: usize, pub count: usize,
pub histogram: OnceCell<TimeGraph>, pub histogram: OnceCell<TimeGraph>,
pub sparkline: OnceCell<String>, pub sparkline: OnceCell<SparkLine>,
pub all: LineSet<'logs>, pub all: LineSet<'logs>,
pub grouped: Vec<LineSet<'logs>>, pub grouped: Vec<LineSet<'logs>>,
} }
@ -77,10 +77,9 @@ impl<'logs> LogMatch<'logs> {
} }
} }
pub fn sparkline(&self, app: &App) -> &str { pub fn sparkline(&self, app: &App) -> &SparkLine {
self.sparkline self.sparkline
.get_or_init(|| self.histogram(app).sparkline::<10>()) .get_or_init(|| self.histogram(app).sparkline())
.as_str()
} }
pub fn histogram(&self, app: &App) -> &TimeGraph { pub fn histogram(&self, app: &App) -> &TimeGraph {
@ -136,7 +135,7 @@ impl<'logs> LogMatch<'logs> {
pub struct LineSet<'logs> { pub struct LineSet<'logs> {
pub lines: Vec<&'logs LogLine<'logs>>, pub lines: Vec<&'logs LogLine<'logs>>,
pub histogram: OnceCell<TimeGraph>, pub histogram: OnceCell<TimeGraph>,
pub sparkline: OnceCell<String>, pub sparkline: OnceCell<SparkLine>,
} }
impl<'logs> LineSet<'logs> { impl<'logs> LineSet<'logs> {
@ -148,10 +147,9 @@ impl<'logs> LineSet<'logs> {
} }
} }
pub fn sparkline(&self, app: &App) -> &str { pub fn sparkline(&self, app: &App) -> &SparkLine {
self.sparkline self.sparkline
.get_or_init(|| self.histogram(app).sparkline::<10>()) .get_or_init(|| self.histogram(app).sparkline())
.as_str()
} }
pub fn histogram(&self, app: &App) -> &TimeGraph { pub fn histogram(&self, app: &App) -> &TimeGraph {

View file

@ -1,4 +1,5 @@
use hdrhistogram::Histogram; use hdrhistogram::Histogram;
use ratatui::text::Text;
use std::cmp::max; use std::cmp::max;
use time::OffsetDateTime; use time::OffsetDateTime;
@ -41,21 +42,44 @@ impl TimeGraph {
.map(|val| val.count_since_last_iteration()) .map(|val| val.count_since_last_iteration())
} }
pub fn sparkline<const N: usize>(&self) -> String { pub fn sparkline(&self) -> SparkLine {
let mut values = [0; N]; let mut values = [0; 10];
for (value, count) in values.iter_mut().zip(self.counts(N)) { for (value, count) in values.iter_mut().zip(self.counts(10)) {
*value = count; *value = count;
} }
let max = values.iter().copied().max().unwrap_or_default() as f64; let max = values.iter().copied().max().unwrap_or_default() as f64;
let len = SPARKS.len() as f64 - 1.0; let len = SPARKS.len() as f64 - 1.0;
values values
.iter()
.copied()
.map(|val| { .map(|val| {
let rel = val as f64 / max; let rel = val as f64 / max;
SPARKS[(rel * len) as usize] SPARKS[(rel * len) as usize]
}) })
.collect() .into()
}
}
// the biggest sparkline char is 3 bytes
pub struct SparkLine {
bytes: [u8; 10 * 3],
}
impl From<[char; 10]> for SparkLine {
fn from(value: [char; 10]) -> Self {
let mut buff = [0; 10 * 3];
let mut offset = 0;
for char in value {
char.encode_utf8(&mut buff[offset..]);
offset += char.len_utf8();
}
SparkLine { bytes: buff }
}
}
impl<'a> From<&'a SparkLine> for Text<'a> {
fn from(value: &'a SparkLine) -> Self {
// SAFETY: we only put bytes into the buffer from encode_utf8
let str = unsafe { str::from_utf8_unchecked(&value.bytes).trim_end_matches(char::from(0)) };
Text::raw(str)
} }
} }