plox 0.3.5

Turn messy logs into clean graphs. Plot fields or regex matches over time, mark events, count occurrences β€” all from your terminal.
Documentation
# plox β€” Time Plots from Logs

## πŸš€ What It Does

You give it logs. It gives you a graph.

Turn timestamped logs into clean plots.  Extract numeric values from log files and plot them over time. Fully CLI-driven. Regex-based. Configurable.

For πŸ“Έ real-world usage examples, see the [sample gallery](https://github.com/michalkucharczyk/plox/blob/master/SAMPLE.md).
Browse full [CLI reference](https://github.com/michalkucharczyk/plox/blob/master/cli-help.md).

## ✨ Features

- Plot over time:
  - Numeric fields (e.g. `"duration: 125ms"`)
  - Event markers when patterns appear
  - Event counts and time deltas between matches

- Parsing logs:
  - Extract numeric values from logs using regex
  - Parse flexible timestamp formats
  - Support unit-aware values
  - Filter log lines using fast string-based guards

- Compose multi-panel layouts to keep metrics organized
- Compare multiple logs using per-file layouts and panel duplication

- Save and reuse graph setups via TOML config files
- Output PNG graphs (via gnuplot) and CSV caches for fast redraws

- Includes tools to explore your data:
  - `stat` β†’ shows summary stats and histogram
  - `cat` β†’ prints raw extracted values


## πŸ§ͺ Examples

The simplest usage, typical first call:
```rust,ignore
plox graph \
	  --input tests/examples/checker.log \
	  --plot duration

```

<img src="https://raw.githubusercontent.com/michalkucharczyk/plox/master/tests/examples/basic.png" width="800" />

More complex usage:
```rust,ignore
plox graph \
	  --input  tests/examples/some.log \
	  --output tests/.output/panels.png \
	  --timestamp-format "[%s]" \
	  --plot om_module x \
	  --panel \
	  --plot x_module x01 \
	  --plot x_module x02 \
	  --plot x_module x03 \
	  --panel \
	  --event-count foo_module SOME_EVENT \
	  --event foo_module SOME_EVENT 1.0 --yaxis y2 --style points

```

<img src="https://raw.githubusercontent.com/michalkucharczyk/plox/master/tests/examples/panels.png" width="800" />

For more examples refer to [sample gallery](https://github.com/michalkucharczyk/plox/blob/master/SAMPLE.md).

See `plox --help` for a complete list of subcommands and options.

---

## πŸ“¦ Install

```sh
cargo install plox
```
or
```sh
git clone git@github.com:michalkucharczyk/plox.git
cd plox
cargo build --release
# use ./target/release/plox 
```

`gnuplot` is required for rendering PNGs β€” install it via your system package manager.

---


## πŸ”§ Advanced topics

In this section:
- [Stats and Raw Values]#-displaying-stats-and-raw-values
- [Multiple Log Files]#-working-with-multiple-log-files
- [Panel Duplication]#-panel-duplication
- [Time Ranges]#-time-ranges-and-alignment
- [Graph Config]#-graph-config
- [Output Files]#-output-files
- [Case Study]#-case-study

## πŸ“Š Displaying Stats and Raw Values

Working with extracted datasets often benefits from quick statistical insight, and `plox` provides built-in tools for that.

```rust,ignore
plox stat \
	  --input tests/examples/checker.log \
	  field-value TRACE duration

```

This command displays basic statistics (count, min, max, mean, median, percentiles) and shows an ASCII histogram to help you quickly understand the distribution of extracted values:
```ignore
 count: 1130
   min: 0.13308
   max: 3.114183
  mean: 1.0390050628318581
median: 1.0636225000000001
   q75: 1.0734786666666667
   q90: 1.2681463333333334
   q95: 1.4730833499999998
   q99: 2.06401263

# Each ∎ is a count of 17
#
    0.1331 -     0.6312 [  66 ]: ∎∎∎
    0.6312 -     1.1293 [ 856 ]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
    1.1293 -     1.6274 [ 171 ]: ∎∎∎∎∎∎∎∎∎∎
    1.6274 -     2.1255 [  34 ]: ∎∎
    2.1255 -     2.6236 [   1 ]:
    2.6236 -     3.1217 [   2 ]:
    3.1217 -     3.6199 [   0 ]:
    3.6199 -     4.1180 [   0 ]:
    4.1180 -     4.6161 [   0 ]:
    4.6161 -     5.1142 [   0 ]:
```

---
### πŸ“‚ Working with Multiple Log Files

Multiple input logs can be passed via `--input`:

```sh
plox graph --input a.log,b.log ...
```

By default, each line (plot, event, etc.) is applied to **all input files** β€” unless it is bound to a specific file.

#### πŸ”— Binding a Line to a Specific File

To target a specific log file:

- Use `--file-id <N>` to refer to the *Nth* file in `--input`
- Or use `--file-name <path>` to bind directly to a filename

```sh
# apply this line only to c.log:
--input a.log,b.log,c.log --plot my-guard duration --file-id 2 

# apply this line to global-errors.log which is not used in --input:
--input a.log,b.log --plot my-guard "duration: ([0-9.]+)" --file-name globab-errors.log
```

#### βœ… Without Binding, Lines Apply to All Inputs

If `--file-id` or `--file-name` is not set, the line is applied to **every file** in `--input`.

This is useful if comparing logs from the same system.

---

### πŸ”„ Panel Duplication

Use `--per-file-panels` to **separate outputs per input log**, which may improve readability of the graph.

This flag automatically duplicates each panel once per input file, **if that panel includes any unbound lines**. Then, it resolves each unbound line to exactly one input.

#### How it works

- Unbound lines (no `file-id` or `file-name`) β†’ assigned to exactly one file per duplicated panel
- Bound lines β†’ copied into all panels unchanged
- Each final panel = one log file + one consistent layout

#### Why it matters

This makes it easy to apply **a single graph layout across multiple logs**, with clean isolation:

```sh
plox graph --input a.log,b.log --per-file-panels \
  --plot worker "duration: ([0-9.]+)" \
  --file-name e.log --event-count "ERROR"
```

Results in:

| Panel  | Line                          | File        |
|--------|-------------------------------|-------------|
| 0      | `duration`                    | a.log       |
| 0      | `ERROR` (explicit file-name)  | e.log       |
| 1      | `duration`                    | b.log       |
| 1      | `ERROR` (explicit file-name)  | e.log       |

This lets applying the same layout to multiple inputs while keeping shared reference lines intact.

---

### πŸ•’ Time Ranges and Alignment

Each panel computes its own time range by merging the time spans of all its lines. This behavior is controlled by `--time-range-mode`, which determines whether the panel uses the full union or only the overlapping portion of its lines.

The X-axis range across panels can then be left independent or aligned globally. This is configured using `--panel-alignment-mode`.

If `--time-range` is provided, it overrides all automatic range calculation and applies a fixed global time window to all panels. Useful for "zooming" some interesting area.

---

### πŸ“ Graph Config

Once the command-line version becomes too complex to maintain comfortably, the configuration can be saved to a TOML file using `-w <graph-config-file>`. This creates a declarative layout that’s easy to version, inspect, and edit.

Below is a sample graph configuration representing where we eventually land in the case study. It’s functionally identical to the CLI commands above but significantly easier to maintain as the graph grows.

```toml
[[panels]]

[[panels.lines]]
guard = "prune:"
field = "validated_counter"
style = "points"
marker_size = 3.0
marker_type = "dot"
marker_color = "red"
title = "validation count in prune"

[[panels.lines]]
guard = "prune"
field = 'took:([\d\.]+)(\w+)?'
style = "points"
marker_size = 3.0
marker_type = "cross"
marker_color = "blue"
title = "prune duration [y2][ms]"
yaxis = "y2"


[[panels]]
panel_title = "txs"
legend = true

[[panels.lines]]
guard = "maintain"
field = 'txs=\((\d+),\s+\d+\)'
style = "steps"
line_color = "red"
title="watched txs"

[[panels.lines]]
guard = "maintain"
field = 'txs=\(\d+,\s+(\d+)\)'
style = "steps"
line_color = "blue"
line_width=2
title="unwatched txs"
```

---

### πŸ“„ Output Files

Running `plox graph` generates:

- `png` β€” rendered plot in given location (default: `graph.png`, or via `--output`)
- `gnuplot` β€” generated script (same location and name as PNG)
- CSV cache per log file (default: `.plox/` next to the log file), can be controlled by `--cache-dir`,

Regenration of CSV cache can be forced with `--force-csv-regen` flag.

Additionally the output PNG can be saved next to the input log file (if one log), or to in a common parent directory (if multiple input files are given) if `--inline-output <FILE>` is used.

---

### πŸ“Š Case Study

This section walks through a real-world example of using `plox` to build a graph configuration progressively via the CLI β€” starting with a single metric, layering in more complexity, and finally extracting it into a reusable config.

### Some log

```bash
cat eve.log | grep "prune:\|maintain"
2025-04-22 09:31:00.885  INFO  maintain txs=(29382, 0) a=1 i=6 views=[(37, 15000, 0)] event=NewBestBlock { hash: 0x0e506bf406bd6ef327c272ab036fe909ce6562608d2dbd34706a49b2f827824f, tree_route: None } duration=56.206398ms
2025-04-22 09:31:13.081 DEBUG  prune: validated_counter=2, known_txs_count: 7191, unknown_txs_count: 2399, reused_txs_count: 2397, took:4.708552ms    
2025-04-22 09:31:13.091 DEBUG  prune: validated_counter=2, known_txs_count: 7191, unknown_txs_count: 2399, reused_txs_count: 2397, took:12.98374ms    
2025-04-22 09:31:13.097 DEBUG  prune: validated_counter=2, known_txs_count: 7191, unknown_txs_count: 2, reused_txs_count: 0, took:17.462902ms    
2025-04-22 09:31:23.246 DEBUG  prune: validated_counter=2, known_txs_count: 952, unknown_txs_count: 2, reused_txs_count: 0, took:4.187313801s    
2025-04-22 09:31:25.586  INFO  maintain txs=(68222, 0) a=2 i=4 views=[(37, 60712, 0), (39, 55211, 0)] event=NewBestBlock { hash: 0x4f9767d3d74b5d8ea24c27b89687353d710e135d7a2807988197a002a87e4727, tree_route: None } duration=6.576908462s
2025-04-22 09:31:25.795 DEBUG  prune: validated_counter=2, known_txs_count: 2397, unknown_txs_count: 2, reused_txs_count: 0, took:16.699269ms    
2025-04-22 09:31:25.935  INFO  maintain txs=(65825, 0) a=2 i=4 views=[(37, 61031, 0), (40, 53840, 0)] event=NewBestBlock { hash: 0x49ebabc44e86b9fd10448b427a75f8414c664ef9d80fa6afc5edb1fe459cdd3f, tree_route: None } duration=231.082854ms
2025-04-22 09:31:31.920 DEBUG  prune: validated_counter=2, known_txs_count: 2397, unknown_txs_count: 2, reused_txs_count: 0, took:540.608Β΅s    
2025-04-22 09:31:31.987  INFO  maintain txs=(78598, 0) a=2 i=5 views=[(37, 73804, 0), (41, 64216, 0)] event=NewBestBlock { hash: 0xaa56cbaa429751a59398eb6a22c252065bac177889020bc76c45a775d4d49b7a, tree_route: None } duration=960.324727ms
2025-04-22 09:31:37.058 DEBUG  prune: validated_counter=2, known_txs_count: 2397, unknown_txs_count: 2, reused_txs_count: 0, took:640.847Β΅s    
2025-04-22 09:31:37.125  INFO  maintain txs=(76201, 0) a=2 i=5 views=[(37, 73804, 0), (42, 61819, 0)] event=NewBestBlock { hash: 0xa429dfe56c6cf9b01e28313c7bcf72b5964794fe62dbb5c3ade3c21827379e18, tree_route: None } duration=146.739374ms
2025-04-22 09:31:42.837 DEBUG  prune: validated_counter=2, known_txs_count: 2397, unknown_txs_count: 2, reused_txs_count: 0, took:565.728Β΅s    
2025-04-22 09:31:42.897  INFO  maintain txs=(78470, 0) a=1 i=5 views=[(43, 64088, 0)] event=NewBestBlock { hash: 0x0308aedb05641a4418c43e0c064e516834182157fa3d8017abf28a5fb117e5db, tree_route: None } duration=160.865349ms
2025-04-22 09:31:48.966 DEBUG  prune: validated_counter=2, known_txs_count: 2397, unknown_txs_count: 2, reused_txs_count: 0, took:623.898Β΅s    
...
2025-04-22 09:32:36.622 DEBUG tokio-runtime-worker txpool: prune: validated_counter=2, known_txs_count: 2397, unknown_txs_count: 2, reused_txs_count: 0, took:648.029Β΅s    
2025-04-22 09:32:43.346 DEBUG tokio-runtime-worker txpool: prune: validated_counter=2, known_txs_count: 2397, unknown_txs_count: 2, reused_txs_count: 0, took:599.498Β΅s    
```

### πŸ–₯️ CLI Workflow

A sequence of `plox graph` commands that evolve from a simple one-liner to a complete multi-panel, multi-metric graph.

```sh
# basic plot: extract one value from logs
plox graph --input eve.log \
  --plot prune "validated_counter"

# add another value: prune duration
plox graph --input eve.log \
  --plot prune "validated_counter" \
  --plot prune "took:([\d.]+)(\w+)?"

# split into separate panels for clarity
plox graph --input eve.log \
  --plot prune "validated_counter" \
  --panel \
  --plot prune "took:([\d.]+)(\w+)?"

# switch to points with marker size
plox graph --input eve.log \
  --plot prune "validated_counter" --style points --marker-size 3 \
  --panel \
  --plot prune "took:([\d.]+)(\w+)?" --style points --marker-size 3

# write a graph config file (too complex for cli - file editing becomes more convenient)
plox graph --input eve.log \
  --plot prune "validated_counter" --style points --marker-size 3 \
  --panel \
  --plot prune "took:([\d.]+)(\w+)?" --style points --marker-size 3 \
  -w prune.toml

# try to extract first number from 'txs=(.., ..)' in 'maintain...' lines:
graph --input eve.log  \
   --plot maintain "txs=\((\d+),\s+\d+\)"

# edit/iterate the config, extract more data
plox graph --input eve.log -c prune.toml -o graph-old.png

# compare to another run
plox graph --input eve-new.log -c prune.toml -o graph-new.png

# apply same config to multiple files with per-file layout
plox graph --input charlie.log,ferdie.log,dave.log,eve.log \
  -c prune.toml --per-file-panels -o graph-all.png
```

---

## 🚧 Status

Actively under development β€” feedback welcome!

## πŸ“ License

Licensed under either of:

- MIT License
- Apache License 2.0

at your option.