#plot #logging #panel #duration #graph #alignment #input

bin+lib plox

Turn messy logs into clean graphs. Plot fields or regex matches over time, mark events, count occurrences — all from your terminal.

8 releases

Uses new Rust 2024

new 0.3.5 May 17, 2025
0.3.4 May 14, 2025
0.2.0 Apr 27, 2025
0.1.0 Apr 19, 2025

#31 in Visualization

Download history 112/week @ 2025-04-14 78/week @ 2025-04-21 323/week @ 2025-04-28 94/week @ 2025-05-05 204/week @ 2025-05-12

731 downloads per month

MIT/Apache

4MB
5.5K SLoC

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. Browse full CLI reference.

✨ 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:

plox graph \
	  --input tests/examples/checker.log \
	  --plot duration

More complex usage:

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

For more examples refer to sample gallery.

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


📦 Install

cargo install plox

or

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:

📊 Displaying Stats and Raw Values

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

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:

 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:

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

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.

[[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

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.

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

Dependencies

~27–40MB
~693K SLoC