Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions cli-help.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ Data sources - plotted line types:
<pattern>: Substring or regex pattern to match in log lines


--field-value-sum <guard> <field>
Plot a cumulative sum of numeric field from logs
<guard>: Optional guard string to quickly filter out log lines using `strcmp`
<field>: The name of the field to parse as numeric or regex. Refer to "Plot Field Regex" help section for more details


--plot <guard> <field>
Plot a numeric field from logs
<guard>: Optional guard string to quickly filter out log lines using `strcmp`
Expand Down Expand Up @@ -159,7 +165,7 @@ Input files:
-i, --input <INPUT>
Input log files to be processed. Comma-separated list of input log files to be processed

--timestamp-format <TIMESTAMP_FORMAT>
-r, --timestamp-format <TIMESTAMP_FORMAT>
The format of the timestamp which is used in logs.

For exact format specifiers refer to: <https://docs.rs/chrono/latest/chrono/format/strftime/index.html>
Expand All @@ -171,6 +177,11 @@ Input files:

Ignores invalid timestamps. Useful when log contains line with invalid or no timestamp (e.g. stacktraces).

--guard <GUARDS>
Optional guard strings to quickly filter out log lines using `strcmp`.

Only lines containing all guards will be passed for plotting data extraction.

-c, --config <FILE>
Path to TOML config file containing panels layout.

Expand Down Expand Up @@ -259,14 +270,15 @@ The tool is designed to parse timestamped logs. The timestamp format used in the
For the the exact format specifiers refer to: https://docs.rs/chrono/latest/chrono/format/strftime/index.html

Examples:
- "2025-04-03 11:32:48.027" | "%Y-%m-%d %H:%M:%S%.3f"
- "08:26:13 AM" | "%I:%M:%S %p"
- "2025 035 08:26:13 AM" | "%Y %j %I:%M:%S %p"
- "035 08:26:13 AM" | "%j %I:%M:%S %p"
- "[1577834199]" | "[%s]"
- "1577834199" | "%s"
- "Apr 20 08:26:13 AM" | "%b %d %I:%M:%S %p"
- "[100.333]" | not supported...
- "2025-04-03 11:32:48.027" | "%Y-%m-%d %H:%M:%S%.3f"
- "2025-06-10T12:08:41.600447Z" | "%Y-%m-%dT%H:%M:%S%.6fZ"
- "08:26:13 AM" | "%I:%M:%S %p"
- "2025 035 08:26:13 AM" | "%Y %j %I:%M:%S %p"
- "035 08:26:13 AM" | "%j %I:%M:%S %p"
- "[1577834199]" | "[%s]"
- "1577834199" | "%s"
- "Apr 20 08:26:13 AM" | "%b %d %I:%M:%S %p"
- "[100.333]" | not supported...

Field regex:
Regex pattern shall contain a single capture group for matching value only, or two
Expand Down
17 changes: 9 additions & 8 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ The tool is designed to parse timestamped logs. The timestamp format used in the
For the the exact format specifiers refer to: https://docs.rs/chrono/latest/chrono/format/strftime/index.html

<underline>Examples</underline>:
- "2025-04-03 11:32:48.027" | "%Y-%m-%d %H:%M:%S%.3f"
- "08:26:13 AM" | "%I:%M:%S %p"
- "2025 035 08:26:13 AM" | "%Y %j %I:%M:%S %p"
- "035 08:26:13 AM" | "%j %I:%M:%S %p"
- "[1577834199]" | "[%s]"
- "1577834199" | "%s"
- "Apr 20 08:26:13 AM" | "%b %d %I:%M:%S %p"
- "[100.333]" | not supported...
- "2025-04-03 11:32:48.027" | "%Y-%m-%d %H:%M:%S%.3f"
- "2025-06-10T12:08:41.600447Z" | "%Y-%m-%dT%H:%M:%S%.6fZ"
- "08:26:13 AM" | "%I:%M:%S %p"
- "2025 035 08:26:13 AM" | "%Y %j %I:%M:%S %p"
- "035 08:26:13 AM" | "%j %I:%M:%S %p"
- "[1577834199]" | "[%s]"
- "1577834199" | "%s"
- "Apr 20 08:26:13 AM" | "%b %d %I:%M:%S %p"
- "[100.333]" | not supported...

<bold><underline>Field regex:</underline></bold>
Regex pattern shall contain a single capture group for matching value only, or two
Expand Down
18 changes: 18 additions & 0 deletions src/data_source_cli_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ impl DataSource {
const CLI_NAME_EVENT: &str = "event";
const CLI_NAME_EVENT_COUNT: &str = "event-count";
const CLI_NAME_EVENT_DELTA: &str = "event-delta";
const CLI_NAME_FIELD_VALUE_SUM: &str = "field-value-sum";

pub fn get_cli_ids() -> Vec<String> {
DummyDataSourceSubcommand::command()
Expand Down Expand Up @@ -93,6 +94,23 @@ impl DataSource {
)));
},
},
Self::CLI_NAME_FIELD_VALUE_SUM => match val.len() {
1 => DataSource::FieldValueSum(FieldCaptureSpec {
guard: None,
field: val[0].to_string(),
}),
2 => DataSource::FieldValueSum(FieldCaptureSpec {
guard: Some(val[0].to_string()),
field: val[1].to_string(),
}),
_ => {
return Err(Error::GeneralCliParseError(format!(
"Bad parameter count ({}) for {}. This is bug.",
val.len(),
id
)));
},
},
Self::CLI_NAME_EVENT_COUNT => match val.len() {
1 => DataSource::EventCount { guard: None, pattern: val[0].to_string() },
2 => DataSource::EventCount {
Expand Down
6 changes: 3 additions & 3 deletions src/gnuplot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ pub fn write_gnuplot_script(
title = ?panel.title(),
"No data points for panel.");
};
for (j, line) in non_empty_lines {
for (idx, (line_index, line)) in non_empty_lines.iter().enumerate() {
let mut style_parts: Vec<String> = Vec::new();

style_parts.push(line.line.params.style.to_gnuplot().into());
Expand Down Expand Up @@ -316,13 +316,13 @@ pub fn write_gnuplot_script(

write!(
file,
" csv_data_file_{j:04} using (combine_datetime('date','time')):'{}' {} title '{}'",
" csv_data_file_{line_index:04} using (combine_datetime('date','time')):'{}' {} title '{}'",
line.csv_data_column_for_plot(),
style,
line.title(has_multiple_input_files),
)?;

if j != panel.lines.len() - 1 {
if idx != non_empty_lines.len() - 1 {
gpwr!(file, ", \\")?;
} else {
gpwr!(file, "")?;
Expand Down
57 changes: 57 additions & 0 deletions src/graph_cli_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,11 @@ mod tests {
self.line = Some(DataSource::FieldValue(FieldCaptureSpec { guard, field }));
self
}

pub fn with_field_value_sum_line(mut self, guard: Option<String>, field: String) -> Self {
self.line = Some(DataSource::FieldValueSum(FieldCaptureSpec { guard, field }));
self
}
}

#[test]
Expand Down Expand Up @@ -1132,6 +1137,58 @@ mod tests {
)
}

#[test]
fn test_15() {
check_ok(
vec!["--field-value-sum", "c1", "d"],
"tests/test-files/config15.toml",
GraphConfigBuilder::new()
.with_default_panel()
.with_line(
LineBuilder::new()
.with_field_value_sum_line(Some("c1".into()), "d".into())
.build()
.unwrap(),
)
.build(),
);
}

#[test]
fn test_16() {
check_ok(
vec![
"--field-value-sum",
"duration",
"--file-name",
"x.log",
"--yaxis",
"y2",
"--line-color",
"red",
"--marker-type",
"circle",
"--marker-color",
"blue",
],
"tests/test-files/config16.toml",
GraphConfigBuilder::new()
.with_default_panel()
.with_line(
LineBuilder::new()
.with_field_value_sum_line(None, "duration".into())
.apply_param(LineParam::LineColor("red".into()))
.apply_param(LineParam::MarkerType("circle".into()))
.apply_param(LineParam::MarkerColor("blue".into()))
.apply_param(LineParam::YAxis(YAxis::Y2))
.apply_param(LineParam::InputFileName("x.log".into()))
.build()
.unwrap(),
)
.build(),
)
}

#[test]
#[should_panic(expected = "invalid value")]
fn test_e00() {
Expand Down
21 changes: 21 additions & 0 deletions src/graph_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ pub struct InputFilesContext {
/// [default: '%Y-%m-%d %H:%M:%S%.3f']
#[arg(
long,
short = 'r',
default_value = None,
help_heading = "Input files",
)]
Expand All @@ -132,6 +133,13 @@ pub struct InputFilesContext {
#[arg(long, short = 't', default_value_t = false, help_heading = "Input files")]
#[serde(skip)]
ignore_invalid_timestamps: bool,

/// Optional guard strings to quickly filter out log lines using `strcmp`.
///
/// Only lines containing all guards will be passed for plotting data extraction.
#[arg(long = "guard", help_heading = "Input files")]
#[serde(default)]
guards: Vec<String>,
}

/// Global graph context shared across all panels and lines.
Expand Down Expand Up @@ -277,6 +285,10 @@ impl InputFilesContext {
pub fn ignore_invalid_timestamps(&self) -> bool {
self.ignore_invalid_timestamps
}

pub fn guards(&self) -> &Vec<String> {
&self.guards
}
}

/// Determines the output file paths, based on selected backend.
Expand All @@ -301,6 +313,8 @@ impl GraphFullContext {
set_if_none!(output_graph_ctx.per_file_panels);
set_if_none!(output_graph_ctx.inline_output);
set_if_none!(input_files_ctx.timestamp_format);

self.input_files_ctx.guards.extend(other.input_files_ctx.guards);
}

pub fn new_with_input(input: Vec<PathBuf>) -> Self {
Expand Down Expand Up @@ -623,6 +637,9 @@ pub enum DataSource {
/// Plot the time delta between consecutive occurrences of `pattern`.
EventDelta(EventDeltaSpec),

/// Plot a cumulative sum of numeric field from logs.
FieldValueSum(FieldCaptureSpec),

/// Plot a numeric field from logs.
///
/// This is the most common data source type.
Expand All @@ -647,6 +664,10 @@ impl DataSource {
pub fn new_plot_field(guard: Option<String>, field: String) -> Self {
DataSource::FieldValue(FieldCaptureSpec { guard, field })
}

pub fn new_field_sum(guard: Option<String>, field: String) -> Self {
DataSource::FieldValueSum(FieldCaptureSpec { guard, field })
}
}

/// Which Y-axis to plot a line against.
Expand Down
19 changes: 13 additions & 6 deletions src/plotly_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::process::Command;
use std::{fs::File, io};
use std::{io::BufReader, num::ParseFloatError};
use tracing::warn;
use tracing::{debug, info};
use tracing::{debug, info, trace};

//todo:
// - logging
Expand Down Expand Up @@ -108,16 +108,17 @@ impl MarkerType {

impl From<MarkerSize> for usize {
fn from(val: MarkerSize) -> Self {
(val.0).round() as usize
(2.0 * val.0).round() as usize
}
}

#[derive(Serialize)]
#[derive(Serialize, Debug)]
struct PanelTemplateInput {
id: String,
title: String,
traces_json: String,
yaxis_scale: String,
time_range: Option<(String, String)>,
}

fn build_trace(
Expand Down Expand Up @@ -202,23 +203,29 @@ pub fn write_plotly_html_inner(
continue;
}
let id = format!("plot{}", panel_idx);
debug!(target:LOG_TARGET,"drawing {id}: {:#?}",panel);
trace!(target:LOG_TARGET,"drawing {id}: {:#?}",panel);
let mut traces = vec![];

for line in &panel.lines {
traces.push(build_trace(context, line)?);
}

let traces_json = serde_json::to_string(&traces)?;
panels.push(PanelTemplateInput {
let panel = PanelTemplateInput {
id,
traces_json,
title: panel.title().join(" | ").to_string(),
yaxis_scale: match panel.params.yaxis_scale {
Some(AxisScale::Linear) | None => "linear".to_string(),
Some(AxisScale::Log) => "log".to_string(),
},
});
time_range: panel.time_range.map(|(start, end)| {
let format = "%Y-%m-%d %H:%M:%S";
(format!("{}", start.format(format)), format!("{}", end.format(format)))
}),
};
// debug!(target:APPV,"panel: {:?}", panel);
panels.push(panel);
}

let raw_template = include_str!("../templates/plotly_template.html"); // relative to this Rust file
Expand Down
Loading