# 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:
| 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
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.