PyLatencyMap is a terminal-based visualizer for latency histograms.
It’s intended to help with performance tuning and troubleshooting.
It renders two scrolling heat maps—Frequency and Intensity—so you can see how latency distributions evolve over time.
Works from the command line and plays nicely with sources that output latency histograms (Oracle wait histograms,
BPF/bcc, DTrace, SystemTap, tracefiles, etc.).
From PyPI:
pip install PyLatencyMap
Check it’s on PATH (one of):
latencymap --help
# or
python -m LatencyMap --help
git clone https://github.com/LucaCanali/PyLatencyMap
cd PyLatencyMap
python LatencyMap.py --help
Requires Python 3.x and a terminal that supports ANSI colors.
Try PyLatencyMap with sample data
Sample data is provided in SampleData/. For a quick visualization:
pip install PyLatencyMap
cat SampleData/example_latency_data.txt | latencymap
Optionally slow down playback:
cat SampleData/example_latency_data.txt | latencymap --screen_delay=0.2
The following assume the visualizer is installed pip install PyLatencyMap and available as
latencymap (or as python -m LatencyMap).
# Oracle troubleshooting, measure I/O random reads and sample every 3 seconds
sqlplus -S system/manager@mydb \
@Event_histograms_oracle/ora_latency_micro.sql "db file sequential read" 3 \
| latencymap
# Oracle troubleshooting, measure commit time
sqlplus -S / as sysdba \
@Event_histograms_oracle/ora_latency_micro.sql "log file sync" 3 \
| latencymap
# Requires bcc installed and sudo privileges
sudo bash
dnf install bcc*
python -u BPF-bcc/pylatencymap-biolatency.py -QT 3 100|python LatencyMap.py
# Parse 10046 trace, filter for "db file sequential read" waits
cat SampleData/test_10046_tracefile.trc|python 10046_trace_oracle/10046_connector.py |python LatencyMap.py
# Requires compatible kernel, debuginfo, and stap privileges
# Install SystemTap and prepare the system on Fedora/RHEL:
sudo bash
dnf install -y systemtap systemtap-runtime
stap-prep
stap -v SystemTap/blockio_rq_issue_pylatencymap.stp 3 | python LatencyMap.py
# Example with recorded data
cat SampleData/test_SystemTap_data.txt|python SystemTap/systemtap_connector.py|python LatencyMap.py
# example with a DTrace script measuring pread latency
dtrace -s DTrace/pread_latency.d |python DTrace/dtrace_connector.py |python LatencyMap.py
PyLatencyMap is pipe-friendly: a data source emits records, you may pass them through an optional connector to adapt the format, and finally pipe to the visualizer:
data_source | [optional_connector] | latencymap [options]
# or
data_source | [optional_connector] | python -m LatencyMap [options]
Rendering latency histograms over time is a 3D problem (latency × time × magnitude). Heat maps make it tractable—but you need two projections:
1) Frequency heat map — How often events land in each bucket (events/sec). 2) Intensity heat map — How much time those events consume (ms/sec or unit/sec).
A system might show a bright band < 1 ms in Frequency (most ops are fast) while a thin, hotter band around 8–20 ms in Intensity reveals a tail that dominates end-to-end time. Both views matter.
PyLatencyMap reads tagged records from stdin. Each record is delimited by <begin record> / <end record> and contains metadata plus cumulative counts per bucket (the tool computes deltas between records).
<begin record>
timestamp,microsec,<epoch_usecs>,<human_readable_ts>
latencyunit,<millisec|microsec|nanosec>
label,<free text>
datasource,<|bpf|systemtap|dtrace|oracle>
<power_of_two_value>,<cumulative_count>
<power_of_two_value>,<cumulative_count>
...
<end record>
Conventions
latencyunit declares the unit used by bucket values; the Y-axis labels are always shown in milliseconds.1, 2, 4, 8, …, 2^N in the declared unit).datasource influences how Intensity is approximated from counts:oracle: ~ 0.75 * bucket_value * waitsbpf / systemtap / dtrace: ~ 1.5 * bucket_value * waitsSampleData/example_latency_data.txt for a concrete example.--num_records=INT Number of time intervals (columns). Default: 90
--min_bucket=INT Lower bucket exponent (log2). -1 = autotune (default)
--max_bucket=INT Upper bucket exponent (log2). 64 = autotune (default)
--frequency_maxval=F Fix the color scale max for frequency; -1 = auto (default)
--intensity_maxval=F Fix the color scale max for intensity; -1 = auto (default)
--screen_delay=FLOAT Delay (s) between screens; useful for replays. Default: 0.1
--debug_level=INT 0..5 (verbosity/diagnostics). Default: 0
Notes
microsec inputs (common), autotune sets min_bucket = 7 (i.e., 128 µs) and a compact vertical range.*_maxval is useful to make colors comparable across runs..512, .256, …; bottom row is <.1281, 2, 4, 8, … (no leading dot)(<unit>/sec); labels are still in ms).Patterns to watch
PyLatencyMap works live, but you can also record input to a file and replay it later (slower, with --screen_delay).
You can record a live feed to a file using Linux's tee for later analysis or playback:
# Record
data_source | tee /tmp/latency_feed.txt | optional_connector | latencymap
# Replay later (slower)
cat /tmp/latency_feed.txt | optional_connector | latencymap --screen_delay=0.2
<begin/end record> tags.latencyunit is correct (millisec|microsec|nanosec).--min_bucket / --max_bucket.--frequency_maxval and --intensity_maxval.LatencyMap.py # Main visualizer (this tool)
SampleData/ # Example recorded inputs
SystemTap/, BPF-bcc/, DTrace/
Event_histograms_oracle/, AWR_oracle/, 10046_trace_oracle/
NetApp_Cmode/
Example*.sh # Turnkey scripts per source
tnsnames.ora # Helper for Oracle examples
pyproject.toml # Packaging metadata
LICENSE # Project license
dist/ # Built artifacts (when present)
Luca Canali — CERN
📧 Luca.Canali@cern.ch
🌐 https://cern.ch/canali
See LICENSE in the repository.
$ claude mcp add PyLatencyMap \
-- python -m otcore.mcp_server <graph>